From 2bd2786c6ae42a8f75a658721d12dcdab8170e6a Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Wed, 16 Sep 2026 09:10:15 +0800 Subject: [PATCH] feat(mobile): align native remote controls with mobile web --- scripts/generate-i18n-contract.mjs | 8 + src/apps/desktop/src/api/commands.rs | 35 +- src/apps/desktop/src/api/path_target.rs | 64 ++- src/apps/mobile/README.md | 31 +- src/apps/mobile/android/AGENTS.md | 8 + .../mobile/app/ChatMessageBubbleTest.kt | 2 +- .../mobile/app/ConversationViewTest.kt | 168 ++++-- .../mobile/app/QuestionDraftTest.kt | 92 ++++ .../mobile/app/RuntimeFileBrowserTest.kt | 84 +++ .../mobile/app/RuntimeFileEditorTest.kt | 45 ++ .../mobile/app/RuntimeTerminalViewTest.kt | 123 +++++ .../mobile/app/RuntimeWorkspacePickerTest.kt | 45 ++ .../mobile/app/ToolConfirmationPanelTest.kt | 25 + .../mobile/app/ToolStatusRowTest.kt | 2 +- .../app/platform/AppLocaleController.kt | 10 +- .../app/platform/GeneratedLocaleContract.kt | 4 + .../app/ui/chat/ConversationTimelineView.kt | 16 +- .../mobile/app/ui/chat/ConversationView.kt | 109 ++-- .../mobile/app/ui/chat/MarkdownContent.kt | 17 +- .../app/ui/chat/tool/ToolInteractionPanels.kt | 76 ++- .../mobile/app/ui/chat/tool/ToolStatusList.kt | 17 +- .../app/ui/remote/CreateSessionScreen.kt | 18 +- .../mobile/app/ui/remote/DeviceToolsDialog.kt | 87 +++ .../app/ui/remote/DocumentExportPolicy.kt | 6 + .../mobile/app/ui/remote/PairingScreen.kt | 24 +- .../app/ui/remote/RemoteDownloadSaver.kt | 10 +- .../app/ui/remote/RemoteSessionListView.kt | 3 +- .../app/ui/remote/RuntimeTerminalView.kt | 34 +- .../app/ui/remote/RuntimeWorkspaceDialogs.kt | 246 +++++++-- .../ui/remote/RuntimeWorkspacePickerDialog.kt | 97 ++++ .../mobile/app/ui/shell/MobileScreen.kt | 41 +- .../mobile/app/ui/shell/sidebar/AppSidebar.kt | 37 +- .../app/ui/shell/sidebar/SidebarChrome.kt | 7 +- .../sidebar/SidebarRemoteWorkspaceSection.kt | 108 +++- .../ui/theme/generated/MobileDesignTokens.kt | 7 + .../res/drawable-nodpi/approval_shield.png | Bin 0 -> 1324 bytes .../app/src/main/res/values-zh/strings.xml | 8 + .../app/src/main/res/values/strings.xml | 8 + .../ui/chat/tool/ToolInteractionPanelsTest.kt | 4 +- .../app/ui/remote/DocumentExportPolicyTest.kt | 15 + .../design-system/assets/approval-shield.svg | 43 ++ .../components/mobile-components.json | 44 ++ .../preview/generated/mobile-design-data.js | 102 +++- .../design-system/tokens/mobile-tokens.json | 11 +- .../main/ets/entryability/EntryAbility.ets | 5 +- .../main/ets/generated/MobileDesignTokens.ets | 6 + .../entry/src/main/ets/i18n/EnUsMessages.ets | 10 + .../entry/src/main/ets/i18n/ZhCnMessages.ets | 10 + .../src/main/ets/model/InteractionMailbox.ets | 51 ++ .../entry/src/main/ets/model/RemoteModels.ets | 17 +- .../actions/AppRootPresentationActions.ets | 8 +- .../ets/pages/actions/ConversationIntent.ets | 8 + .../actions/ConversationIntentDispatcher.ets | 3 + .../components/AppRootOverlaySurfaces.ets | 15 +- .../main/ets/pages/components/AppSidebar.ets | 46 +- .../pages/components/ChatMessageBubble.ets | 4 + .../ets/pages/components/ChatTimeline.ets | 12 +- .../ets/pages/components/ConversationView.ets | 65 +-- .../components/InteractionMailboxPanel.ets | 88 ++++ .../ets/pages/components/MarkdownContent.ets | 22 +- .../ets/pages/components/RecentRemoteHome.ets | 41 +- .../pages/components/SidebarDeviceGroup.ets | 98 ++-- .../components/SidebarWorkspaceSection.ets | 134 ++--- .../ets/pages/components/SubagentTaskCard.ets | 3 + .../src/main/ets/pages/components/Theme.ets | 1 + .../components/ToolInteractionPanels.ets | 34 +- .../ets/pages/components/ToolStatusList.ets | 23 +- .../pages/components/WideConversationHost.ets | 15 +- .../components/WorkspaceTerminalView.ets | 4 + .../pages/components/WorkspaceToolsPanel.ets | 180 ++++--- .../components/remote/RemoteSurfaceHost.ets | 20 +- .../pages/policy/RemoteCompactHomePolicy.ets | 13 +- .../preview/InteractionMailboxPreview.ets | 36 ++ .../ets/pages/preview/MobileDesignGallery.ets | 3 + .../main/ets/pages/runtime/AppRootRuntime.ets | 73 ++- .../runtime/AppRootRuntimeComposition.ets | 55 +- .../ets/pages/state/ConversationCoreState.ets | 4 + .../ets/pages/state/ConversationViewState.ets | 7 + .../ets/pages/state/DeviceDirectoryState.ets | 45 +- .../main/ets/pages/state/RemotePageState.ets | 42 +- .../ets/pages/state/WorkspaceToolsState.ets | 10 + .../viewmodel/ConversationController.ets | 5 +- .../viewmodel/DeviceDirectoryViewModel.ets | 111 +++- .../viewmodel/RemoteActivityViewModel.ets | 83 +-- .../viewmodel/RemoteConnectionController.ets | 2 +- .../viewmodel/RemoteCreateFlowController.ets | 6 +- .../viewmodel/RemoteSessionViewModel.ets | 12 +- .../viewmodel/RemoteWorkspaceViewModel.ets | 54 +- .../pages/viewmodel/SettingsController.ets | 9 +- .../viewmodel/WorkspaceToolsViewModel.ets | 86 ++- .../ets/services/AccountDeviceDirectory.ets | 15 +- .../ets/services/ChatSessionController.ets | 89 ++-- .../services/DeviceDirectoryCoordinator.ets | 35 +- .../ets/services/DurableSessionReducer.ets | 22 +- .../ets/services/DurableSessionStream.ets | 46 +- .../main/ets/services/HostCatalogObserver.ets | 4 +- .../ets/services/InteractionMailboxStore.ets | 121 +++++ .../ets/services/PermissionControlOverlay.ets | 93 ---- .../services/RemoteChatCommandController.ets | 5 +- .../RemoteChatPollingLifecycleController.ets | 5 + .../ets/services/RemoteCommandFactory.ets | 8 +- .../ets/services/RemoteSessionController.ets | 10 +- .../ets/services/RemoteSessionIdentity.ets | 36 +- .../ets/services/RemoteSessionManager.ets | 78 ++- .../services/RemoteWorkspaceCoordinator.ets | 9 +- .../services/RemoteWorkspaceRepository.ets | 12 +- .../main/ets/services/WorkspaceCatalog.ets | 48 ++ .../services/WorkspaceFileUploadClient.ets | 31 +- .../harmonyos/entry/src/main/module.json5 | 1 + .../main/resources/base/element/color.json | 4 + .../resources/base/media/approval_shield.png | Bin 0 -> 1324 bytes .../main/resources/dark/element/color.json | 4 + .../resources/rawfile/terminal/terminal.js | 34 +- .../ConversationPresentationUnit.test.ets | 3 +- .../src/test/DeviceDirectoryUnit.test.ets | 6 + .../entry/src/test/LocalTestFixtures.ets | 6 + .../tools/tests/account-restore.test.cjs | 37 ++ .../tools/tests/connection-health.test.cjs | 218 ++++++++ .../tests/device-directory-lifecycle.test.cjs | 179 +++++++ .../tools/tests/durable-session.test.cjs | 159 ++++++ .../tools/tests/host-catalog.test.cjs | 12 +- .../tools/tests/interaction-mailbox.test.cjs | 214 ++++++++ .../tools/tests/permission-mode.test.cjs | 39 ++ .../tests/session-model-hydration.test.cjs | 69 +++ .../tools/tests/session-record.test.cjs | 114 ++-- .../tools/tests/terminal-lifecycle.test.cjs | 102 ++++ .../tools/tests/workspace-catalog.test.cjs | 94 ++++ .../tools/tests/workspace-editor.test.cjs | 91 +++- .../tests/workspace-refresh-race.test.cjs | 51 ++ .../tools/tests/workspace-upload.test.cjs | 59 ++- .../App/MobileLaunchConfiguration.swift | 15 +- .../Features/Chat/ChatTimelineView.swift | 218 ++++++-- .../Features/Chat/ConversationHeader.swift | 1 - .../GeneratedMobileDesignTokens.swift | 6 + .../Features/Remote/RemoteHomeViews.swift | 17 +- .../Features/Shell/MobileShellView.swift | 36 +- .../Shell/RemoteCreateSessionView.swift | 327 ++++++++++-- .../Shell/RemoteFilePreviewView.swift | 1 + .../Features/Shell/SidebarView.swift | 134 +++-- .../MobileAppModel+Account.swift | 14 +- .../MobileAppModel+FilePreview.swift | 17 +- .../MobileAppModel+RemoteSession.swift | 173 ++++-- .../Infrastructure/MobileAppModel.swift | 39 +- .../Infrastructure/MobileCoreAdapter.swift | 53 +- .../Infrastructure/MobileLocalization.swift | 10 +- .../Models/MobilePresentationModels.swift | 14 +- .../ApprovalShield.imageset/Contents.json | 12 + .../approval_shield.png | Bin 0 -> 1324 bytes .../Resources/Localizable.xcstrings | 116 +++- .../RemoteCodeSessionSendUITests.swift | 398 +++++++++++++- src/apps/mobile/ios/README.md | 6 +- .../Testing/RemoteAuthorityGateTests.swift | 22 +- .../mobile/core/domain/ConversationModels.kt | 7 +- .../core/domain/RemoteWorkspaceIdentity.kt | 30 ++ .../domain/RemoteWorkspaceIdentityTest.kt | 37 ++ .../core/feature/account/AccountStore.kt | 110 ++-- .../feature/directory/DeviceDirectoryStore.kt | 113 ++-- .../directory/DeviceDirectoryUiState.kt | 39 +- .../feature/session/PermissionMailboxStore.kt | 70 ++- .../session/RecentSessionsPresentation.kt | 21 + .../feature/session/RemoteSessionStore.kt | 298 +++++++---- .../feature/session/RemoteSessionUiState.kt | 61 ++- .../feature/session/SessionRecordReplica.kt | 48 +- .../shell/RemoteSidebarPresentation.kt | 43 +- .../feature/workspace/RemoteWorkspaceStore.kt | 108 +++- .../workspace/RemoteWorkspaceUiState.kt | 34 +- .../feature/workspace/RuntimeFilesStore.kt | 91 +++- .../feature/workspace/RuntimeTerminalStore.kt | 53 +- .../workspace/WorkspaceCatalogUiState.kt | 52 ++ .../core/feature/account/AccountStoreTest.kt | 163 +++++- .../directory/DeviceDirectoryStoreTest.kt | 82 ++- .../WorkspaceSessionPersistenceTest.kt | 29 + .../session/PermissionMailboxStoreTest.kt | 179 ++++++- .../session/RecentSessionsPresentationTest.kt | 23 + .../feature/session/RemoteSessionStoreTest.kt | 498 +++++++++++++++++- .../session/SessionRecordReplicaTest.kt | 100 ++++ .../shell/RemoteSidebarPresentationTest.kt | 35 +- .../workspace/RemoteWorkspaceStoreTest.kt | 130 ++++- .../workspace/RuntimeFilesStoreTest.kt | 162 +++++- .../workspace/RuntimeTerminalStoreTest.kt | 130 ++++- .../feature/workspace/WorkspaceCatalogTest.kt | 52 ++ .../mobile/core/persistence/ChatLocalStore.kt | 9 + .../mobile/core/protocol/SessionDtos.kt | 1 + .../mobile/core/protocol/WorkspaceDtos.kt | 2 + .../shared/core-transport/build.gradle.kts | 3 + .../mobile/core/transport/AccountRealtime.kt | 28 +- .../core/transport/CloudAccountClient.kt | 30 +- .../mobile/core/transport/RelayHttpClient.kt | 4 +- .../mobile/core/transport/SessionStream.kt | 26 +- .../core/transport/CloudAccountClientTest.kt | 71 +++ .../core/transport/SessionStreamTest.kt | 85 +++ .../core/transport/OkHttpRealtimeTest.kt | 101 ++++ src/crates/assembly/core/AGENTS.md | 3 + .../src/service/filesystem/path_operations.rs | 78 ++- .../core/src/service/remote_connect/mod.rs | 2 + .../remote_connect/permission_publication.rs | 104 ++++ .../core/src/service_agent_runtime.rs | 89 +++- .../native-terminal-renderer-browser.test.mjs | 17 +- src/shared/i18n/contract/locales.json | 1 + .../terminal/webview/generated/terminal.js | 34 +- src/shared/terminal/webview/index.ts | 35 +- 201 files changed, 9022 insertions(+), 1811 deletions(-) create mode 100644 src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/QuestionDraftTest.kt create mode 100644 src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileBrowserTest.kt create mode 100644 src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileEditorTest.kt create mode 100644 src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeTerminalViewTest.kt create mode 100644 src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeWorkspacePickerTest.kt create mode 100644 src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/GeneratedLocaleContract.kt create mode 100644 src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DeviceToolsDialog.kt create mode 100644 src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicy.kt create mode 100644 src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspacePickerDialog.kt create mode 100644 src/apps/mobile/android/app/src/main/res/drawable-nodpi/approval_shield.png create mode 100644 src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicyTest.kt create mode 100644 src/apps/mobile/design-system/assets/approval-shield.svg create mode 100644 src/apps/mobile/harmonyos/entry/src/main/ets/model/InteractionMailbox.ets create mode 100644 src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/InteractionMailboxPanel.ets create mode 100644 src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/InteractionMailboxPreview.ets create mode 100644 src/apps/mobile/harmonyos/entry/src/main/ets/services/InteractionMailboxStore.ets delete mode 100644 src/apps/mobile/harmonyos/entry/src/main/ets/services/PermissionControlOverlay.ets create mode 100644 src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceCatalog.ets create mode 100644 src/apps/mobile/harmonyos/entry/src/main/resources/base/media/approval_shield.png create mode 100644 src/apps/mobile/harmonyos/tools/tests/account-restore.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/connection-health.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/device-directory-lifecycle.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/interaction-mailbox.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/permission-mode.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/session-model-hydration.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/terminal-lifecycle.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/workspace-catalog.test.cjs create mode 100644 src/apps/mobile/harmonyos/tools/tests/workspace-refresh-race.test.cjs create mode 100644 src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/Contents.json create mode 100644 src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/approval_shield.png create mode 100644 src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentity.kt create mode 100644 src/apps/mobile/shared/core-domain/src/commonTest/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentityTest.kt create mode 100644 src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentation.kt create mode 100644 src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogUiState.kt create mode 100644 src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/WorkspaceSessionPersistenceTest.kt create mode 100644 src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentationTest.kt create mode 100644 src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogTest.kt create mode 100644 src/apps/mobile/shared/core-transport/src/jvmTest/kotlin/com/openbitfun/mobile/core/transport/OkHttpRealtimeTest.kt create mode 100644 src/crates/assembly/core/src/service/remote_connect/permission_publication.rs diff --git a/scripts/generate-i18n-contract.mjs b/scripts/generate-i18n-contract.mjs index 8153549ded..42c5ca063b 100644 --- a/scripts/generate-i18n-contract.mjs +++ b/scripts/generate-i18n-contract.mjs @@ -7,6 +7,14 @@ const checkOnly = process.argv.includes('--check'); const contractPath = path.join(root, 'src', 'shared', 'i18n', 'contract', 'locales.json'); const outputs = [ + { + path: path.join(root, 'src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/GeneratedLocaleContract.kt'), + generate: (contract) => `// Generated by scripts/generate-i18n-contract.mjs. Do not edit. +package com.openbitfun.mobile.app.platform + +internal const val DEFAULT_APP_LANGUAGE_TAG = "${contract.surfaceDefaults.android}" +`, + }, { path: path.join(root, 'src', 'web-ui', 'src', 'infrastructure', 'i18n', 'presets', 'generatedLocaleContract.ts'), generate: generateWebLocaleContract, diff --git a/src/apps/desktop/src/api/commands.rs b/src/apps/desktop/src/api/commands.rs index 7c22a7f17b..c04b7985cb 100644 --- a/src/apps/desktop/src/api/commands.rs +++ b/src/apps/desktop/src/api/commands.rs @@ -2249,11 +2249,22 @@ pub async fn scan_workspace_info( .map_err(|e| format!("Failed to scan workspace info: {}", e)) } -async fn ensure_directory_request_path(path: &str) -> Result<(), String> { +async fn ensure_directory_request_path( + path: &str, + remote_connection_id: Option<&str>, +) -> Result<(), String> { use openbitfun_core::service::remote_ssh::workspace_state::is_remote_path; use std::path::Path; - if is_remote_path(path).await { + if let Some(id) = remote_connection_id.filter(|id| !id.trim().is_empty()) { + openbitfun_core::service::filesystem::path_operations::resolve_explicit_path_connection( + path, + id.trim(), + ) + .await?; + return Ok(()); + } + if remote_connection_id != Some("") && is_remote_path(path).await { return Ok(()); } @@ -2308,7 +2319,7 @@ async fn get_file_tree_response( ) -> Result { use std::path::Path; - ensure_directory_request_path(&request.path).await?; + ensure_directory_request_path(&request.path, request.remote_connection_id.as_deref()).await?; let preferred = request.remote_connection_id.as_deref(); let filesystem_service = &state.filesystem_service; @@ -2345,7 +2356,7 @@ async fn get_directory_children_response( state: &State<'_, AppState>, request: &GetDirectoryChildrenRequest, ) -> Result { - ensure_directory_request_path(&request.path).await?; + ensure_directory_request_path(&request.path, request.remote_connection_id.as_deref()).await?; let preferred = request.remote_connection_id.as_deref(); let filesystem_service = &state.filesystem_service; @@ -2368,7 +2379,7 @@ async fn get_directory_children_paginated_response( let offset = request.offset.unwrap_or(0); let limit = request.limit.unwrap_or(100); - ensure_directory_request_path(&request.path).await?; + ensure_directory_request_path(&request.path, request.remote_connection_id.as_deref()).await?; let preferred = request.remote_connection_id.as_deref(); let filesystem_service = &state.filesystem_service; @@ -2383,6 +2394,14 @@ async fn get_directory_children_paginated_response( request.sort_order.as_deref(), )?; let total = nodes.len(); + debug!( + "Directory page read: explicit_local={}, remote_scope={}, offset={}, limit={}, total={}", + preferred == Some(""), + preferred.is_some_and(|value| !value.is_empty()), + offset, + limit, + total + ); let has_more = total > offset.saturating_add(limit); let page_nodes: Vec<_> = nodes.into_iter().skip(offset).take(limit).collect(); @@ -4680,7 +4699,7 @@ pub async fn search_files( } } } else { - match resolve_desktop_path_target( + match crate::api::path_target::resolve_desktop_workspace_path_target( &state, &request.root_path, request.remote_connection_id.as_deref(), @@ -4763,7 +4782,7 @@ pub async fn search_filenames( include_directories: request.include_directories, }; - let result = match resolve_desktop_path_target( + let result = match crate::api::path_target::resolve_desktop_workspace_path_target( &state, &request.root_path, request.remote_connection_id.as_deref(), @@ -4922,7 +4941,7 @@ pub async fn start_search_filenames_stream( include_directories: request.include_directories, }; - let remote_search_target = match resolve_desktop_path_target( + let remote_search_target = match crate::api::path_target::resolve_desktop_workspace_path_target( &state, &request.root_path, request.remote_connection_id.as_deref(), diff --git a/src/apps/desktop/src/api/path_target.rs b/src/apps/desktop/src/api/path_target.rs index 7309206051..7306ef7252 100644 --- a/src/apps/desktop/src/api/path_target.rs +++ b/src/apps/desktop/src/api/path_target.rs @@ -16,8 +16,14 @@ use serde::Serialize; use std::path::{Path, PathBuf}; use std::time::SystemTime; +/// Resolved file routing identity, independent of workspace registration. #[derive(Debug, Clone)] -pub enum DesktopPathTarget { +pub struct RemotePathConnection { + pub connection_id: String, +} + +#[derive(Debug, Clone)] +pub enum DesktopPathTarget { Local { requested_path: String, resolved_path: PathBuf, @@ -25,7 +31,7 @@ pub enum DesktopPathTarget { }, Remote { requested_path: String, - entry: RemoteWorkspaceEntry, + entry: T, }, } @@ -165,22 +171,24 @@ pub async fn resolve_desktop_path_target( let explicit_connection_id = preferred_remote_connection_id .map(str::trim) .filter(|value| !value.is_empty()); + if let Some(id) = explicit_connection_id { + let connection_id = openbitfun_core::service::filesystem::path_operations::resolve_explicit_path_connection(raw_path, id).await?; + return Ok(DesktopPathTarget::Remote { + requested_path: raw_path.to_string(), + entry: RemotePathConnection { connection_id }, + }); + } if let Some(entry) = lookup_remote_entry_for_path(app_state, raw_path, explicit_connection_id).await { return Ok(DesktopPathTarget::Remote { requested_path: raw_path.to_string(), - entry, + entry: RemotePathConnection { + connection_id: entry.connection_id, + }, }); } - if let Some(connection_id) = explicit_connection_id { - return Err(format!( - "Remote workspace connection '{}' is unavailable or does not own path '{}'; local filesystem fallback was not attempted", - connection_id, raw_path - )); - } - Ok(DesktopPathTarget::Local { requested_path: raw_path.to_string(), resolved_path: PathBuf::from(raw_path), @@ -188,6 +196,42 @@ pub async fn resolve_desktop_path_target( }) } +/// Workspace search retains registered-root scope even when device file tools +/// use a saved SSH connection outside any opened workspace. +pub async fn resolve_desktop_workspace_path_target( + app_state: &AppState, + path: &str, + preferred: Option<&str>, +) -> Result, String> { + match resolve_desktop_path_target(app_state, path, preferred).await? { + DesktopPathTarget::Local { + requested_path, + resolved_path, + is_runtime_artifact, + } => Ok(DesktopPathTarget::Local { + requested_path, + resolved_path, + is_runtime_artifact, + }), + DesktopPathTarget::Remote { + requested_path, + entry, + } => { + let workspace = lookup_remote_entry_for_path( + app_state, + &requested_path, + Some(&entry.connection_id), + ) + .await + .ok_or("Remote search requires a registered workspace owning the requested path")?; + Ok(DesktopPathTarget::Remote { + requested_path, + entry: workspace, + }) + } + } +} + #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct LocalFileMetadata { diff --git a/src/apps/mobile/README.md b/src/apps/mobile/README.md index 166102dcb1..3dc586d385 100644 --- a/src/apps/mobile/README.md +++ b/src/apps/mobile/README.md @@ -98,15 +98,21 @@ The mobile apps control a selected Desktop or CLI runtime. Workspace paths, file Android/iOS conversation synchronization uses the same revisioned rich session records for initial history, live updates, and recovery. The latest page opens first; earlier encrypted pages prefetch in the background through the same single flight used by explicit history requests. Complete fragment boundaries, stable record revisions, and ancestor deletion markers are preserved during backward reads. Encrypted fragments, the forward cursor, and the older-page boundary commit atomically to the local replica. Returning to the foreground actively checks the forward cursor even when the socket did not report a disconnect. +HarmonyOS also publishes the latest page before scheduling older-page prefetch. +Background and manual history requests share the same serialized reader, and +leaving the session cancels scheduled prefetch. + Workspace tools expose directory browsing, text editing with runtime-enforced content-hash conflict detection, file/folder creation, file renaming/deletion, binary upload/download, and bundled xterm PTY control. The terminal executes on the selected runtime or its saved SSH connection; durable notifications request incremental output from the runtime cursor. Pending approvals are a separate runtime mailbox, refreshed at initial attachment, reconnect/foreground recovery, and relevant permission control events. Android/iOS answer the stable request identity even when no tool call is attached, and support approving edited JSON input through the same runtime permission owner. Android, iOS, and HarmonyOS terminal WebViews share `src/shared/terminal/webview` assets. The native adapter only bridges keyboard input, dimensions, and cursor-derived output; it has no Relay token or local process access. Android/iOS downloads stage chunks into a controller-local temporary file and hand its URL or input stream to the platform document exporter. They do not retain the full download in a Kotlin ByteArray or Swift Data. Preview buffers have their own bounded presentation policy. -Android and iOS expose a device-tools button at the bottom of the sidebar. -It selects a runtime location (the controlled device or one of its saved SSH -connections), then opens files or terminals independently of any workspace. +Android, iOS, and HarmonyOS expose a device-tools button at the bottom of the sidebar. +The page selects a runtime location (the controlled device or one of its saved SSH +connections) and provides Files and Terminal tabs independently of any workspace. +Switching tabs retains the selected location and does not create a terminal; +opening a PTY is an explicit action. Local defaults come from the runtime's `get_system_info.homeDir`; SSH defaults to `/`. A successful directory change captures that directory and provider for file transfers. No workspace registration or current-workspace switch is required. @@ -119,10 +125,9 @@ connection, plus server-side name and modification-time sorting before directory pagination. File editing opens a separate full-screen native view; returning preserves the directory and asks before discarding edits. Its line-number gutter shares vertical scrolling with unwrapped source text and never changes file -contents. Rename and delete remain available in that editor. The terminal opens -in a separate native view whose PTY viewport follows the remaining safe-area and -keyboard-adjusted space; leaving the view keeps the terminal running until an -explicit close. Native build checks cover integration, while keyboard, scrolling, +contents. Rename and delete remain available in that editor. The terminal tab +fits its PTY viewport into the remaining safe-area and keyboard-adjusted space; +leaving the page keeps the terminal running until an explicit close. Native build checks cover integration, while keyboard, scrolling, and live runtime behavior still require device or simulator interaction checks. Creating an ordinary chat sends `Claw` without a workspace override and lets the @@ -134,10 +139,14 @@ project carries its connection identity through the create request. ## Connection recovery -Native iOS and Android controllers probe idle session lists while the app is in the -foreground. An open transcript uses its durable session stream for recovery instead -of duplicating the health request. Temporary transport failures keep the displayed -list or transcript; a successful response restores the connected state. +Native iOS and Android controllers probe the selected runtime while in the +foreground, including idle open conversations. Each probe has a ten-second +deadline, and the next starts fifteen seconds after the previous one finishes. +Backgrounding cancels the probe. Durable replay recovers conversation records, +but Relay availability alone does not prove the execution host is online. +Temporary transport failures keep the displayed list, transcript, and draft; a +successful response restores the connected state. Connection feedback stays in +the sidebar instead of inserting a banner above the conversation. Account sign-in on Android, iOS and HarmonyOS opens the shared authorization page with separate GitHub and email-code options. Email users need no password and are diff --git a/src/apps/mobile/android/AGENTS.md b/src/apps/mobile/android/AGENTS.md index b4c9d6b3c0..5b5ba284ed 100644 --- a/src/apps/mobile/android/AGENTS.md +++ b/src/apps/mobile/android/AGENTS.md @@ -52,6 +52,9 @@ JAVA_HOME='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/Applications/Android Studio.app/Contents/jbr/Contents/Home' ./gradle # Verify running-input, plan gating, offline tools, and attachment retention. ./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.openbitfun.mobile.app.MobileParityTest +# Verify terminal WebView focus and native keyboard routing inside the dialog. +./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.openbitfun.mobile.app.RuntimeTerminalViewTest + # Run shared JVM tests after core-feature changes. cd ../shared JAVA_HOME='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/Applications/Android Studio.app/Contents/jbr/Contents/Home' ./gradlew jvmTest @@ -60,3 +63,8 @@ JAVA_HOME='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/Applications/Android Studio.app/Contents/jbr/Contents/Home' ./gradle Use the narrowest command covering the changed behavior. Keep credentials, keystores, signing values, device identifiers, and local SDK paths out of the repository. + +Run instrumented Gradle tests on a dedicated test emulator, not an authenticated +manual-acceptance device. The test runner can uninstall the target application +after the suite and remove its local account state. Use `adb install -r` for +manual acceptance updates to preserve that state. diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ChatMessageBubbleTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ChatMessageBubbleTest.kt index 4a14aa99a6..cbc3dbfd97 100644 --- a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ChatMessageBubbleTest.kt +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ChatMessageBubbleTest.kt @@ -229,7 +229,7 @@ class ChatMessageBubbleTest { ChatMessageBubble( row = row, enabled = true, - onApproveTool = {}, + onApproveTool = { _, _ -> }, onRejectTool = { _, _ -> }, onCancelTool = { _, _ -> }, onAnswerTool = { _, _ -> }, diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ConversationViewTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ConversationViewTest.kt index 0d3c992c8b..1dc74f7ae8 100644 --- a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ConversationViewTest.kt +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ConversationViewTest.kt @@ -4,12 +4,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.graphics.toPixelMap +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.assert import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.captureToImage @@ -17,6 +19,7 @@ import androidx.compose.ui.test.getUnclippedBoundsInRoot import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextReplacement @@ -26,9 +29,7 @@ import androidx.compose.ui.unit.dp import androidx.test.platform.app.InstrumentationRegistry import com.openbitfun.mobile.app.ui.chat.CONVERSATION_LIST_TEST_TAG import com.openbitfun.mobile.app.ui.chat.CONVERSATION_LOADING_TEST_TAG -import com.openbitfun.mobile.app.ui.chat.CHAT_STATUS_DOT_TEST_TAG import com.openbitfun.mobile.app.ui.chat.CHAT_STATUS_BAR_TEST_TAG -import com.openbitfun.mobile.app.ui.chat.ChatStatusBar import com.openbitfun.mobile.app.ui.chat.ConversationEmptyState import com.openbitfun.mobile.app.ui.chat.ConversationTimelineView import com.openbitfun.mobile.app.ui.chat.COMPOSER_INPUT_TEST_TAG @@ -53,6 +54,84 @@ class ConversationViewTest { @get:Rule val composeRule = createComposeRule() + @Test + fun permissionDraftSurvivesSavedStateRestoration() { + val restoration = androidx.compose.ui.test.junit4.StateRestorationTester(composeRule) + val intents = mutableListOf() + val mailbox = com.openbitfun.mobile.core.feature.session.PermissionMailboxUiState(listOf( + com.openbitfun.mobile.core.feature.session.PermissionMailboxRequest( + "request-1", "write", listOf("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/workspace/readme.md"), null, "Write") + ), false, false) + restoration.setContent { + OpenBitFunTheme(dark = false) { + com.openbitfun.mobile.app.ui.chat.PermissionMailboxView(mailbox, "session", 600.dp, intents::add) + } + } + val draft = """{"content":"retained edit"}""" + composeRule.onNodeWithText(string(R.string.tool_edit_approval_input)).performClick() + composeRule.onNodeWithText("{}").performTextReplacement(draft) + restoration.emulateSavedInstanceStateRestore() + composeRule.onNodeWithText(draft).assertIsDisplayed() + composeRule.onNodeWithText(string(R.string.tool_approve)).performClick() + val reply = intents.filterIsInstance().single() + assertEquals("request-1", reply.requestId) + assertEquals(draft, reply.updatedInput) + } + + @Test + fun permissionDraftDoesNotBlockRejectionAndMailboxStaysOutsideComposer() { + val intents = mutableListOf() + val state = readyState(sessionId = "session").copy(permissionMailbox = + com.openbitfun.mobile.core.feature.session.PermissionMailboxUiState(listOf( + com.openbitfun.mobile.core.feature.session.PermissionMailboxRequest( + "request-1", "write", listOf("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/workspace/readme.md"), null, "Write") + ), false, false)) + val dark = androidx.test.platform.app.InstrumentationRegistry.getArguments().getString("mailboxDark") == "true" + setConversationContent(state = { state }, onIntent = intents::add, dark = dark) + composeRule.onNodeWithText("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/workspace/readme.md").assertIsDisplayed() + val capture = composeRule.onRoot().captureToImage().asAndroidBitmap() + val context = androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().targetContext + val values = android.content.ContentValues().apply { + put(android.provider.MediaStore.Downloads.DISPLAY_NAME, "openbitfun-permission-mailbox-${if (dark) "dark" else "light"}.png") + put(android.provider.MediaStore.Downloads.MIME_TYPE, "image/png") + } + val uri = requireNotNull(context.contentResolver.insert(android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)) + requireNotNull(context.contentResolver.openOutputStream(uri)).use { + capture.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, it) + } + val requestBounds = composeRule.onNodeWithText("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/workspace/readme.md").getUnclippedBoundsInRoot() + val composerBounds = composeRule.onNodeWithTag(COMPOSER_INPUT_TEST_TAG).getUnclippedBoundsInRoot() + assertTrue(requestBounds.bottom < composerBounds.top) + composeRule.onNodeWithText(string(R.string.tool_edit_approval_input)).performClick() + composeRule.onNodeWithText("{}").performTextReplacement("{invalid") + composeRule.onNodeWithText(string(R.string.tool_approve)).assertIsNotEnabled() + composeRule.onNodeWithText(string(R.string.tool_reject)).performScrollTo().performClick() + val reply = intents.filterIsInstance().single() + assertEquals("request-1", reply.requestId) + assertEquals(false, reply.approve) + assertEquals(null, reply.updatedInput) + } + + @Test + fun multiplePermissionsScrollIndependentlyWithoutHidingComposer() { + val intents = mutableListOf() + val requests = (1..10).map { index -> + com.openbitfun.mobile.core.feature.session.PermissionMailboxRequest( + "request-$index", "write", listOf("/workspace/file-$index.md"), null, "Write") + } + val state = readyState(sessionId = "session").copy(permissionMailbox = + com.openbitfun.mobile.core.feature.session.PermissionMailboxUiState(requests, false, false)) + setConversationContent(state = { state }, onIntent = intents::add) + composeRule.onNodeWithTag(COMPOSER_INPUT_TEST_TAG).assertIsDisplayed() + val composerBefore = composeRule.onNodeWithTag(COMPOSER_INPUT_TEST_TAG).getUnclippedBoundsInRoot() + composeRule.onAllNodesWithText(string(R.string.tool_reject))[9].performScrollTo().assertIsDisplayed().performClick() + composeRule.onNodeWithTag(COMPOSER_INPUT_TEST_TAG).assertIsDisplayed() + assertEquals(composerBefore, composeRule.onNodeWithTag(COMPOSER_INPUT_TEST_TAG).getUnclippedBoundsInRoot()) + val reply = intents.filterIsInstance().single() + assertEquals("request-10", reply.requestId) + assertEquals(false, reply.approve) + } + @Test fun openingATallLastMessageStartsAtTheActualTail() { val answer = List(500) { "A long answer line." }.joinToString(" ") + " tail-marker" @@ -64,7 +143,7 @@ class ConversationViewTest { hasMoreMessages = false, onLoadOlder = {}, enabled = true, - onApproveTool = {}, + onApproveTool = { _, _ -> }, onRejectTool = { _, _ -> }, onCancelTool = { _, _ -> }, onAnswerTool = { _, _ -> }, @@ -286,66 +365,49 @@ class ConversationViewTest { } @Test - fun reconnectingStatusBarMatchesTheFixedHeightColorAndCopyContract() { - composeRule.setContent { - OpenBitFunTheme(dark = false) { - ChatStatusBar( - phase = ConnectionPhase.RECONNECTING, - canStop = false, - onStop = {}, - ) - } - } - - val title = string(R.string.chat_status_restoring_connection) - val detail = string(R.string.connection_reconnecting_desktop) - composeRule.onNodeWithText("$title ยท $detail").assertExists() - val bounds = composeRule.onNodeWithTag(CHAT_STATUS_BAR_TEST_TAG).getUnclippedBoundsInRoot() - assertTrue(kotlin.math.abs((bounds.bottom - bounds.top).value - 48f) < 1f) - val dot = composeRule.onNodeWithTag(CHAT_STATUS_DOT_TEST_TAG).captureToImage() - assertEquals(0xFF706F6A.toInt(), dot.toPixelMap()[dot.width / 2, dot.height / 2].toArgb()) - } - - @Test - fun executingStatusBarDoesNotAppendAConnectionDetail() { - composeRule.setContent { - OpenBitFunTheme(dark = false) { - ChatStatusBar( - phase = ConnectionPhase.RECONNECTING, - canStop = true, - onStop = {}, - ) - } - } - - composeRule.onNodeWithText(string(R.string.chat_status_executing)).assertExists() + fun reconnectingKeepsConversationAndComposerWithoutHeaderBanner() { + setConversationContent( + state = { readyState() }, + phase = ConnectionPhase.RECONNECTING, + ) + composeRule.onNodeWithTag(CHAT_STATUS_BAR_TEST_TAG).assertDoesNotExist() + composeRule.onNodeWithTag(COMPOSER_INPUT_TEST_TAG).assertIsDisplayed() + composeRule.onNodeWithTag(COMPOSER_SEND_TEST_TAG).assertIsNotEnabled() } private fun setConversationContent( state: () -> RemoteSessionUiState.Ready, phase: ConnectionPhase = ConnectionPhase.CONNECTED, onIntent: (RemoteSessionIntent) -> Unit = {}, + dark: Boolean = false, ) { composeRule.setContent { - OpenBitFunTheme(dark = false) { - ConversationView( - state = state(), - phase = phase, - settingsPlacement = SettingsPlacement(SettingsPlacementMode.BOTTOM, 0, 0, 0), - onBack = {}, - onIntent = onIntent, - contextTitle = "Test desktop", - onOpenFile = { _, _ -> }, - previewingRemotePath = "", - previewLoading = false, - download = RemoteFileDownloadUiState.None, - onDownloadFile = { _, _ -> }, + OpenBitFunTheme(dark = dark) { + androidx.compose.material3.Surface( modifier = Modifier.fillMaxSize(), - ) + color = androidx.compose.material3.MaterialTheme.colorScheme.background, + contentColor = androidx.compose.material3.MaterialTheme.colorScheme.onBackground, + ) { + ConversationView( + state = state(), + phase = phase, + settingsPlacement = SettingsPlacement(SettingsPlacementMode.BOTTOM, 0, 0, 0), + onBack = {}, + onIntent = onIntent, + contextTitle = "Test desktop", + onOpenFile = { _, _ -> }, + previewingRemotePath = "", + previewLoading = false, + download = RemoteFileDownloadUiState.None, + onDownloadFile = { _, _ -> }, + modifier = Modifier.fillMaxSize(), + ) + } } } } + private fun readyState( sessionId: String = "", draft: String = "", @@ -372,7 +434,7 @@ class ConversationViewTest { hasMoreMessages = hasMoreMessages, onLoadOlder = {}, enabled = true, - onApproveTool = {}, + onApproveTool = { _, _ -> }, onRejectTool = { _, _ -> }, onCancelTool = { _, _ -> }, onAnswerTool = { _, _ -> }, diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/QuestionDraftTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/QuestionDraftTest.kt new file mode 100644 index 0000000000..908c0f3e90 --- /dev/null +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/QuestionDraftTest.kt @@ -0,0 +1,92 @@ +package com.openbitfun.mobile.app + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.unit.dp +import com.openbitfun.mobile.app.ui.chat.PermissionMailboxView +import com.openbitfun.mobile.core.feature.session.* +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.test.platform.app.InstrumentationRegistry +import com.openbitfun.mobile.app.ui.chat.tool.ToolQuestionAnswerPanel +import com.openbitfun.mobile.app.ui.theme.OpenBitFunTheme +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +class QuestionDraftTest { + @get:Rule val compose = createComposeRule() + + @Test fun mailboxOwnsTheOnlyAnswerFormWhileTranscriptKeepsItsSummary() { + val question = ToolCard("question", "AskUserQuestion", ToolPhase.RUNNING, + ToolKind.QUESTION, ToolOperation.UNKNOWN, "", "", "", "", "", "One answer only", setOf(ToolAction.ANSWER)) + val mailbox = mutableStateOf(PermissionMailboxUiState(emptyList(), false, false, listOf(question))) + compose.setContent { + OpenBitFunTheme(dark = false) { + androidx.compose.runtime.CompositionLocalProvider( + com.openbitfun.mobile.app.ui.chat.tool.LocalPermissionMailbox provides mailbox.value, + ) { + androidx.compose.foundation.layout.Column { + PermissionMailboxView(mailbox.value, "session", 400.dp) {} + com.openbitfun.mobile.app.ui.chat.tool.ToolStatusList( + listOf(question), true, { _, _ -> }, { _, _ -> }, { _, _ -> }, + { _, _ -> }, { _, _ -> }, { _, _ -> }, androidx.compose.ui.Modifier, + ) + } + } + } + } + compose.onAllNodes(hasSetTextAction()).assertCountEquals(1) + compose.onNodeWithTag(com.openbitfun.mobile.app.ui.chat.tool.TOOL_ROW_TEST_TAG).assertExists() + compose.onNode(hasSetTextAction()).performTextReplacement("Single draft") + compose.onNodeWithText("One answer only").assertExists() + compose.runOnIdle { mailbox.value = mailbox.value.copy(questions = emptyList(), ownedToolIds = setOf(question.id)) } + compose.onAllNodes(hasSetTextAction()).assertCountEquals(0) + compose.onNodeWithTag(com.openbitfun.mobile.app.ui.chat.tool.TOOL_ROW_TEST_TAG).assertExists() + // No mailbox authority on a legacy peer: its transcript still provides the answer UI. + compose.runOnIdle { mailbox.value = PermissionMailboxUiState(emptyList(), false, false) } + compose.onAllNodes(hasSetTextAction()).assertCountEquals(1) + } + + @Test fun removingAnEarlierMailboxQuestionPreservesTheRemainingDraft() { + fun question(id: String) = ToolCard(id, "AskUserQuestion", ToolPhase.RUNNING, + ToolKind.QUESTION, ToolOperation.UNKNOWN, "", "", "", "", "", "Question $id", setOf(ToolAction.ANSWER)) + val questions = mutableStateOf(listOf(question("first"), question("second"))) + compose.setContent { + OpenBitFunTheme(dark = false) { + PermissionMailboxView(PermissionMailboxUiState(emptyList(), false, false, questions.value), "session", 700.dp) {} + } + } + compose.onAllNodes(hasSetTextAction())[1].performTextReplacement("Second question draft") + compose.runOnIdle { questions.value = questions.value.drop(1) } + compose.onNode(hasSetTextAction()).assertTextContains("Second question draft") + compose.onNodeWithText("Question first").assertDoesNotExist() + } + + @Test fun answerSurvivesPendingAndFailedSubmissionButNewQuestionStartsEmpty() { + val question = mutableStateOf("question-1") + val enabled = mutableStateOf(true) + val answers = mutableListOf() + compose.setContent { + OpenBitFunTheme(dark = false) { + ToolQuestionAnswerPanel(question.value, "Explain the change", enabled.value) { + answers += it + enabled.value = false + } + } + } + val label = InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.tool_answer_send) + compose.onNode(hasSetTextAction()).performTextReplacement("Keep this draft") + compose.onNodeWithText(label).performClick() + compose.onNodeWithText("Keep this draft").assertIsNotEnabled() + compose.runOnIdle { enabled.value = true } + compose.onNode(hasSetTextAction()).assertTextContains("Keep this draft") + compose.onNodeWithText(label).performClick() + compose.runOnIdle { + assertEquals(listOf("Keep this draft", "Keep this draft"), answers) + question.value = "question-2" + enabled.value = true + } + compose.onNode(hasSetTextAction()).assertTextContains("") + compose.onNodeWithText(label).assertIsNotEnabled() + } +} diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileBrowserTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileBrowserTest.kt new file mode 100644 index 0000000000..b7e1303854 --- /dev/null +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileBrowserTest.kt @@ -0,0 +1,84 @@ +package com.openbitfun.mobile.app + +import android.graphics.Bitmap +import java.io.File +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.test.platform.app.InstrumentationRegistry +import com.openbitfun.mobile.app.ui.remote.RuntimeFilesDialog +import com.openbitfun.mobile.app.ui.theme.OpenBitFunTheme +import com.openbitfun.mobile.core.feature.workspace.* +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +class RuntimeFileBrowserTest { + @get:Rule val compose = createComposeRule() + private fun text(id: Int) = InstrumentationRegistry.getInstrumentation().targetContext.getString(id) + + @Test fun changingSortResetsAnchorButAppendingPreservesIt() { + val entries = (0 until 80).map { RuntimeFileUiState("/repo/$it", "entry-$it", false) } + val files = RuntimeFilesUiState("/repo", entries, false, null, "", false, false) + val state = mutableStateOf(RemoteWorkspaceUiState.Ready(emptyList(), emptyList(), null, RemoteFilePreviewUiState.None, false, RemoteFileDownloadUiState.None).copy(files = files)) + compose.setContent { + OpenBitFunTheme(dark = false) { RuntimeFilesDialog(state.value, onBack = {}, onIntent = {}) } + } + compose.onNode(hasScrollToIndexAction()).performScrollToIndex(30) + compose.onNodeWithText("entry-30").assertIsDisplayed() + compose.runOnIdle { + state.value = state.value.copy(files = files.copy(entries = entries + RuntimeFileUiState("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/repo/80", "entry-80", false))) + } + compose.onNodeWithText("entry-30").assertIsDisplayed() + compose.runOnIdle { + state.value = state.value.copy(files = files.copy(sort = RuntimeFileSort.NAME_DESC, entries = entries.reversed())) + } + compose.onNodeWithText("entry-79").assertIsDisplayed() + compose.runOnIdle { + state.value = state.value.copy(files = files.copy(directory = "/other")) + } + compose.onNodeWithText("entry-0").assertIsDisplayed() + } + + private fun capture(name: String) { + compose.waitForIdle() + val instrumentation = InstrumentationRegistry.getInstrumentation() + val bitmap = instrumentation.uiAutomation.takeScreenshot() + File(instrumentation.targetContext.getExternalFilesDir(null), name).outputStream().use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } + bitmap.recycle() + } + + @Test fun rowRenameRetainsFailedDraftAndFastRetryClosesForm() { + val files = RuntimeFilesUiState("/repo", listOf(RuntimeFileUiState("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/repo/report.md", "report.md", false)), false, null, "", false, false) + val state = mutableStateOf(RemoteWorkspaceUiState.Ready(emptyList(), emptyList(), null, RemoteFilePreviewUiState.None, false, RemoteFileDownloadUiState.None).copy(files = files)) + val intents = mutableListOf() + compose.setContent { + OpenBitFunTheme(dark = false) { + RuntimeFilesDialog(state.value, onBack = {}, onIntent = { intent -> + intents += intent + if (intent is RemoteWorkspaceIntent.RenameFileEntry) { + val current = state.value.files + state.value = state.value.copy(files = current.copy(failed = intents.size == 1, completedOperation = current.completedOperation + 1)) + } + }) + } + } + compose.onAllNodes(hasSetTextAction()).assertCountEquals(0) + capture("file-browser-list.png") + compose.onNodeWithContentDescription(text(R.string.workspace_file_actions)).performClick() + compose.onNodeWithText(text(R.string.session_rename)).performClick() + compose.onNode(hasSetTextAction()).performTextReplacement("renamed.md") + compose.onAllNodesWithText(text(R.string.session_rename)).filter(hasClickAction()).onFirst().performClick() + compose.onNode(hasSetTextAction()).assertTextContains("renamed.md") + compose.onNodeWithText(text(R.string.workspace_files_failed)).assertIsDisplayed() + capture("file-browser-failed-form.png") + compose.runOnIdle { + val intent = intents.single() as RemoteWorkspaceIntent.RenameFileEntry + assertEquals("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/repo/report.md", intent.path) + assertEquals("renamed.md", intent.name) + } + compose.onAllNodesWithText(text(R.string.session_rename)).filter(hasClickAction()).onFirst().performClick() + compose.onAllNodes(hasSetTextAction()).assertCountEquals(0) + } +} diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileEditorTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileEditorTest.kt new file mode 100644 index 0000000000..f145d95b3b --- /dev/null +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeFileEditorTest.kt @@ -0,0 +1,45 @@ +package com.openbitfun.mobile.app + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.test.platform.app.InstrumentationRegistry +import com.openbitfun.mobile.app.ui.remote.RuntimeFileEditorDialog +import com.openbitfun.mobile.app.ui.theme.OpenBitFunTheme +import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceIntent +import com.openbitfun.mobile.core.feature.workspace.RuntimeFilesUiState +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test + +class RuntimeFileEditorTest { + @get:Rule val compose = createComposeRule() + private fun text(id: Int) = InstrumentationRegistry.getInstrumentation().targetContext.getString(id) + + @Test fun failedSavePreservesDraftAndCancelledDiscardKeepsEditorOpen() { + val state = mutableStateOf(RuntimeFilesUiState("/repo", emptyList(), false, "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/repo/file.md", "original", false, false)) + val intents = mutableListOf() + compose.setContent { + OpenBitFunTheme(dark = false) { + RuntimeFileEditorDialog(state.value) { intents += it } + } + } + compose.onNode(hasSetTextAction()).performTextReplacement("unsaved draft") + compose.onNodeWithText(text(R.string.workspace_save_file)).performClick() + compose.runOnIdle { + assertEquals("unsaved draft", (intents.single() as RemoteWorkspaceIntent.SaveFile).content) + state.value = state.value.copy(failed = true) + } + compose.onNode(hasSetTextAction()).assertTextEquals("unsaved draft") + compose.onNodeWithText(text(R.string.workspace_files_failed)).assertIsDisplayed() + compose.onNodeWithText(text(R.string.common_back)).performClick() + compose.onNodeWithText(text(R.string.workspace_discard_changes)).assertIsDisplayed() + compose.onNodeWithText(text(R.string.common_cancel)).performClick() + compose.onNode(hasSetTextAction()).assertTextEquals("unsaved draft") + compose.runOnIdle { assertEquals(1, intents.size) } + compose.onNodeWithText(text(R.string.common_back)).performClick() + compose.onNodeWithText(text(R.string.workspace_discard)).performClick() + compose.runOnIdle { assertTrue(intents.last() is RemoteWorkspaceIntent.CloseFileEditor) } + } +} diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeTerminalViewTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeTerminalViewTest.kt new file mode 100644 index 0000000000..67a84bacde --- /dev/null +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeTerminalViewTest.kt @@ -0,0 +1,123 @@ +package com.openbitfun.mobile.app + +import android.view.KeyEvent +import android.webkit.WebView +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.platform.app.InstrumentationRegistry +import com.openbitfun.mobile.app.ui.remote.RuntimeTerminalDialog +import com.openbitfun.mobile.app.ui.theme.OpenBitFunTheme +import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceIntent +import com.openbitfun.mobile.core.feature.workspace.RuntimeTerminalUiState +import java.util.concurrent.CopyOnWriteArrayList +import org.junit.Assert.assertEquals +import org.junit.Assert.assertSame +import org.json.JSONTokener +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test + +class RuntimeTerminalViewTest { + @get:Rule val compose = createComposeRule() + + @Test fun touchingTerminalRoutesNativeKeysToPtyInsteadOfDialogButtons() { + val intents = CopyOnWriteArrayList() + var dismissed = false + compose.setContent { + OpenBitFunTheme(dark = false) { + RuntimeTerminalDialog( + RuntimeTerminalUiState("test-pty", "ready\r\n", false, false), + { intents += it }, + { dismissed = true }, + ) + } + } + compose.waitUntil(10_000) { intents.any { it is RemoteWorkspaceIntent.ResizeTerminal } } + onView(isAssignableFrom(WebView::class.java)).perform(click()).check { view, error -> + if (error != null) throw error + assertTrue("The native WebView must own focus, not only its DOM textarea", view.hasFocus()) + } + val instrumentation = InstrumentationRegistry.getInstrumentation() + instrumentation.sendStringSync("pwd") + instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER) + compose.waitUntil(10_000) { + intents.filterIsInstance().joinToString("") { it.data } == "pwd\r" + } + compose.runOnIdle { + assertFalse(dismissed) + assertFalse(intents.any { it is RemoteWorkspaceIntent.CloseTerminal }) + } + } + + @Test fun switchingThemeKeepsTheNativeTerminalAndItsOutput() { + val dark = mutableStateOf(false) + val intents = CopyOnWriteArrayList() + compose.setContent { + OpenBitFunTheme(dark = dark.value) { + RuntimeTerminalDialog(RuntimeTerminalUiState("theme-pty", "retained output\r\n", false, false), { intents += it }, {}) + } + } + compose.waitUntil(10_000) { intents.any { it is RemoteWorkspaceIntent.ResizeTerminal } } + var terminal: WebView? = null + onView(isAssignableFrom(WebView::class.java)).check { view, error -> + if (error != null) throw error + terminal = view as WebView + } + val webView = requireNotNull(terminal) + fun snapshot(): String { + val result = AtomicReference("") + val completed = CountDownLatch(1) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + webView.evaluateJavascript("JSON.stringify({background:getComputedStyle(document.querySelector('.xterm')).backgroundColor,text:document.querySelector('.xterm-rows').textContent})") { + result.set(JSONTokener(it).nextValue().toString()) + completed.countDown() + } + } + assertTrue("Renderer must answer while changing theme", completed.await(5, TimeUnit.SECONDS)) + return result.get() + } + compose.waitUntil(10_000) { snapshot().contains("retained output") } + val light = snapshot() + compose.runOnIdle { dark.value = true } + compose.waitUntil(10_000) { snapshot() != light } + assertTrue(snapshot().contains("retained output")) + onView(isAssignableFrom(WebView::class.java)).check { view, error -> + if (error != null) throw error + assertSame("A theme update must not recreate the PTY renderer", webView, view) + } + compose.runOnIdle { dark.value = false } + compose.waitUntil(10_000) { snapshot() == light } + assertEquals(light, snapshot()) + assertFalse(intents.any { it is RemoteWorkspaceIntent.CloseTerminal || it is RemoteWorkspaceIntent.ReopenTerminal }) + } + + @Test fun terminalControlsFollowSharedCloseStateAndOfferScopedReopen() { + val intents = CopyOnWriteArrayList() + val state = mutableStateOf(RuntimeTerminalUiState("pty", "", false, false)) + val context = InstrumentationRegistry.getInstrumentation().targetContext + compose.setContent { + OpenBitFunTheme(dark = false) { RuntimeTerminalDialog(state.value, { intents += it }, {}) } + } + compose.onNodeWithText(context.getString(R.string.message_stop)).performClick() + compose.runOnIdle { assertTrue(intents.filterIsInstance().single().data == "\u0003") } + val close = context.getString(R.string.workspace_terminal_close) + compose.onNodeWithText(close).performClick() + compose.runOnIdle { state.value = state.value.copy(busy = true) } + compose.onNodeWithText(close).assertIsNotEnabled() + compose.runOnIdle { state.value = state.value.copy(busy = false, failed = true) } + compose.onNodeWithText(close).assertIsEnabled() + compose.onNodeWithText(context.getString(R.string.workspace_terminal_failed)).assertIsDisplayed() + compose.runOnIdle { state.value = RuntimeTerminalUiState(null, "", false, false) } + compose.onNodeWithText(close).assertDoesNotExist() + compose.onNodeWithText(context.getString(R.string.workspace_open_terminal)).performClick() + compose.runOnIdle { assertTrue(intents.any { it is RemoteWorkspaceIntent.ReopenTerminal }) } + } +} diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeWorkspacePickerTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeWorkspacePickerTest.kt new file mode 100644 index 0000000000..2c9a1a950b --- /dev/null +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/RuntimeWorkspacePickerTest.kt @@ -0,0 +1,45 @@ +package com.openbitfun.mobile.app + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.StateRestorationTester +import androidx.test.platform.app.InstrumentationRegistry +import com.openbitfun.mobile.app.ui.remote.RuntimeWorkspacePickerDialog +import com.openbitfun.mobile.app.ui.theme.OpenBitFunTheme +import com.openbitfun.mobile.core.feature.workspace.* +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +class RuntimeWorkspacePickerTest { + @get:Rule val compose = createComposeRule() + + @Test fun recreationRetainsDraftAndDoesNotReplayWorkspaceMutation() { + val restoration = StateRestorationTester(compose) + val state = mutableStateOf(RemoteWorkspaceUiState.Ready(emptyList(), emptyList(), null, + RemoteFilePreviewUiState.None, false, RemoteFileDownloadUiState.None)) + val intents = mutableListOf() + restoration.setContent { + OpenBitFunTheme(dark = true) { + RuntimeWorkspacePickerDialog(state.value, onIntent = { + intents += it + state.value = state.value.copy(busy = true) + }, onDismiss = {}) + } + } + compose.onNode(hasSetTextAction()).performTextReplacement("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/workspace/draft") + restoration.emulateSavedInstanceStateRestore() + compose.onNode(hasSetTextAction()).assertTextContains("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/workspace/draft") + compose.runOnIdle { assertEquals(0, intents.size) } + val open = InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.workspace_open_path) + compose.onAllNodesWithText(open).filter(hasClickAction()).onFirst().performClick() + restoration.emulateSavedInstanceStateRestore() + compose.runOnIdle { + assertEquals(1, intents.size) + state.value = state.value.copy(busy = false, loadFailure = true) + } + compose.onNode(hasSetTextAction()).assertTextContains("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/workspace/draft") + compose.runOnIdle { assertEquals(1, intents.size) } + } +} diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolConfirmationPanelTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolConfirmationPanelTest.kt index 6a9e3e83dc..ee376fafb9 100644 --- a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolConfirmationPanelTest.kt +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolConfirmationPanelTest.kt @@ -1,5 +1,9 @@ package com.openbitfun.mobile.app +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextReplacement +import org.junit.Assert.assertEquals import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.hasSetTextAction @@ -19,6 +23,7 @@ class ToolConfirmationPanelTest { composeRule.setContent { OpenBitFunTheme(dark = false) { ToolConfirmationPanel( + input = "", canApprove = true, canReject = true, enabled = true, @@ -32,4 +37,24 @@ class ToolConfirmationPanelTest { composeRule.onNodeWithText(testString(R.string.tool_reject)).assertIsDisplayed() composeRule.onAllNodes(hasSetTextAction()).assertCountEquals(0) } + @Test + fun invalidEditedInputDisablesApprovalButKeepsRejectionAvailable() { + var rejected = 0 + var approved = 0 + composeRule.setContent { + OpenBitFunTheme(dark = false) { + ToolConfirmationPanel(canApprove = true, canReject = true, enabled = true, input = "{}", + onApprove = { approved++ }, onReject = { rejected++ }) + } + } + composeRule.onAllNodes(hasSetTextAction()).assertCountEquals(0) + composeRule.onNodeWithText(testString(R.string.tool_edit_approval_input)).performClick() + composeRule.onNode(hasSetTextAction()).performTextReplacement("invalid") + composeRule.onNodeWithText(testString(R.string.tool_approve)).assertIsNotEnabled() + composeRule.onNodeWithText(testString(R.string.tool_reject)).performClick() + composeRule.runOnIdle { assertEquals(1, rejected); assertEquals(0, approved) } + composeRule.onNodeWithText(testString(R.string.tool_hide_approval_input)).performClick() + composeRule.onAllNodes(hasSetTextAction()).assertCountEquals(0) + } + } diff --git a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolStatusRowTest.kt b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolStatusRowTest.kt index ca5d99c37f..7e5c737a80 100644 --- a/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolStatusRowTest.kt +++ b/src/apps/mobile/android/app/src/androidTest/kotlin/com/openbitfun/mobile/app/ToolStatusRowTest.kt @@ -220,7 +220,7 @@ class ToolStatusRowTest { ToolStatusList( tools = listOf(readTool("a", "One.kt"), readTool("b", "Two.kt")), enabled = true, - onApprove = {}, + onApprove = { _, _ -> }, onReject = { _, _ -> }, onCancel = { _, _ -> }, onAnswer = { _, _ -> }, diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/AppLocaleController.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/AppLocaleController.kt index 016f84d18a..443040b7d3 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/AppLocaleController.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/AppLocaleController.kt @@ -54,10 +54,16 @@ internal object AppLocaleController { } fun applySaved(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val manager = context.getSystemService(LocaleManager::class.java) + if (manager.applicationLocales.isEmpty) { + manager.applicationLocales = LocaleList.forLanguageTags(DEFAULT_APP_LANGUAGE_TAG) + } + return + } val tag = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) .getString(LANGUAGE_KEY, null) - ?: return + ?: DEFAULT_APP_LANGUAGE_TAG applyLegacy(context, tag) } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/GeneratedLocaleContract.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/GeneratedLocaleContract.kt new file mode 100644 index 0000000000..c2f3b9f67e --- /dev/null +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/platform/GeneratedLocaleContract.kt @@ -0,0 +1,4 @@ +// Generated by scripts/generate-i18n-contract.mjs. Do not edit. +package com.openbitfun.mobile.app.platform + +internal const val DEFAULT_APP_LANGUAGE_TAG = "zh-CN" diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationTimelineView.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationTimelineView.kt index 39c20bece8..e1f5a3a639 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationTimelineView.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationTimelineView.kt @@ -1,5 +1,7 @@ package com.openbitfun.mobile.app.ui.chat +import com.openbitfun.mobile.core.feature.session.HistoryLoadState + import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -12,6 +14,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -81,6 +84,7 @@ internal fun ConversationTimelineView( onDownloadFile: (String, String) -> Unit, downloadEnabled: Boolean, modifier: Modifier, + historyLoadState: HistoryLoadState = HistoryLoadState.IDLE, ) { val listState = rememberLazyListState() var stickToBottom by rememberSaveable { mutableStateOf(true) } @@ -126,8 +130,16 @@ internal fun ConversationTimelineView( if (hasMoreMessages) { item(key = "load-older-messages") { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - TextButton(onClick = { stickToBottom = false; onLoadOlder() }, enabled = enabled) { - Text(stringResource(R.string.chat_load_older_messages)) + TextButton( + onClick = { stickToBottom = false; onLoadOlder() }, + enabled = enabled && historyLoadState != HistoryLoadState.LOADING, + colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurfaceVariant), + ) { + Text(stringResource(when (historyLoadState) { + HistoryLoadState.LOADING -> R.string.chat_loading_older_messages + HistoryLoadState.FAILED -> R.string.chat_load_older_failed + else -> R.string.chat_load_older_messages + })) } } } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationView.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationView.kt index a87d0ce2a1..f6963132bf 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationView.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/ConversationView.kt @@ -3,6 +3,21 @@ package com.openbitfun.mobile.app.ui.chat import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.ui.Alignment +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontFamily +import com.openbitfun.mobile.app.ui.theme.generated.MobileDesignGeometry +import com.openbitfun.mobile.app.ui.theme.generated.MobileDesignTypography import android.app.Activity import android.content.Intent import android.speech.RecognizerIntent @@ -215,13 +230,16 @@ internal fun ConversationView( } androidx.compose.runtime.CompositionLocalProvider( + com.openbitfun.mobile.app.ui.chat.tool.LocalPermissionMailbox provides state.permissionMailbox, com.openbitfun.mobile.app.ui.chat.tool.LocalPlanActions provides com.openbitfun.mobile.app.ui.chat.tool.PlanActions( supported = "plan_build_v1" in hostCapabilities, enabled = !state.busy && activeTurn == null && phase == ConnectionPhase.CONNECTED, build = { plan -> onIntent(RemoteSessionIntent.BuildPlan(sessionId, plan.path, plan.name)) }, ), ) { - Column(modifier = modifier.fillMaxSize().testTag(CONVERSATION_TEST_TAG)) { + androidx.compose.foundation.layout.BoxWithConstraints(modifier.fillMaxSize()) { + val mailboxMaxHeight = maxHeight * 0.4f + Column(modifier = Modifier.fillMaxSize().testTag(CONVERSATION_TEST_TAG)) { ConversationHeader( title = state.sessions.firstOrNull { it.id == sessionId }?.title.orEmpty(), contextTitle = contextTitle, @@ -241,28 +259,19 @@ internal fun ConversationView( modifier = Modifier, ) - if (phase != ConnectionPhase.CONNECTED && timeline != null) { - ChatStatusBar( - phase = phase, - canStop = activeTurn != null, - onStop = { - onIntent(RemoteSessionIntent.CancelTurn(sessionId, activeTurn?.turnId)) - }, - ) - } - - PermissionMailboxView(state.permissionMailbox, sessionId, onIntent) + key(sessionId) { PermissionMailboxView(state.permissionMailbox, sessionId, mailboxMaxHeight, onIntent) } if (timeline == null) { Box(Modifier.weight(1f).fillMaxWidth()) { if (loadingVisible) ConversationLoadingState(Modifier.fillMaxSize()) } - } else if (visibleRows.isEmpty()) { + } else if (visibleRows.isEmpty() && !state.hasMoreMessages) { ConversationEmptyState(modifier = Modifier.weight(1f).fillMaxWidth()) } else { key(attachmentOwner, sessionId) { ConversationTimelineView( rows = visibleRows, hasMoreMessages = state.hasMoreMessages, + historyLoadState = state.historyLoadState, onLoadOlder = { onIntent(RemoteSessionIntent.LoadOlderMessages) }, enabled = !state.busy, onApproveTool = { toolId, updatedInput -> @@ -362,6 +371,7 @@ internal fun ConversationView( }, ) } + } } @@ -401,37 +411,66 @@ private fun ConversationLoadingState(modifier: Modifier) { } @Composable -private fun PermissionMailboxView(state: com.openbitfun.mobile.core.feature.session.PermissionMailboxUiState, sessionId: String, onIntent: (RemoteSessionIntent) -> Unit) { - Column { +internal fun PermissionMailboxView(state: com.openbitfun.mobile.core.feature.session.PermissionMailboxUiState, sessionId: String, maxHeight: androidx.compose.ui.unit.Dp, onIntent: (RemoteSessionIntent) -> Unit) { + if (!state.failed && state.requests.isEmpty() && state.questions.isEmpty()) return + Column(Modifier.fillMaxWidth().heightIn(max = maxHeight).verticalScroll(rememberScrollState()).padding(horizontal = 16.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(MobileDesignGeometry.ApprovalCardGap)) { if (state.failed) Text(stringResource(R.string.permission_mailbox_failed)) if (state.failed) androidx.compose.material3.TextButton(onClick = { onIntent(RemoteSessionIntent.RefreshPermissionMailbox) }) { Text(stringResource(R.string.sessions_retry)) } state.questions.forEach { question -> - com.openbitfun.mobile.app.ui.chat.tool.ToolStatusList(listOf(question), enabled = !state.busy, - onApprove = { _, _ -> }, onReject = { _, _ -> }, - onCancel = { id, reason -> onIntent(RemoteSessionIntent.CancelTool(sessionId, id, reason)) }, - onAnswer = { id, answer -> onIntent(RemoteSessionIntent.AnswerQuestion(sessionId, id, answer)) }, - onAnswerStructured = { id, answers -> onIntent(RemoteSessionIntent.AnswerStructuredQuestion(sessionId, id, answers)) }, - onOpenFile = { _, _ -> }, modifier = Modifier.fillMaxWidth().pointerInput(question.id) { - awaitEachGesture { awaitFirstDown(requireUnconsumed = false); onIntent(RemoteSessionIntent.StartQuestionInteraction(question.id)) } - }) + androidx.compose.runtime.key(sessionId, question.id) { + Column(Modifier.fillMaxWidth().pointerInput(question.id) { + awaitEachGesture { awaitFirstDown(requireUnconsumed = false); onIntent(RemoteSessionIntent.StartQuestionInteraction(question.id)) } + }) { + if (question.questions.isNotEmpty()) { + com.openbitfun.mobile.app.ui.chat.tool.ToolStructuredQuestionPanel( + toolId = question.id, questions = question.questions, enabled = !state.busy, + onSubmit = { answers -> onIntent(RemoteSessionIntent.AnswerStructuredQuestion(sessionId, question.id, answers)) }) + } else { + com.openbitfun.mobile.app.ui.chat.tool.ToolQuestionAnswerPanel( + toolId = question.id, prompt = question.question ?: stringResource(R.string.tool_question_default), enabled = !state.busy, + onSubmit = { answer -> onIntent(RemoteSessionIntent.AnswerQuestion(sessionId, question.id, answer)) }) + } + } + } } state.requests.forEach { request -> - androidx.compose.runtime.key(request.requestId) { - var edit by remember { mutableStateOf(false) } - var input by remember { mutableStateOf("{}") } + androidx.compose.runtime.key(sessionId, request.requestId) { + var edit by rememberSaveable { mutableStateOf(false) } + var input by rememberSaveable { mutableStateOf("{}") } val valid = !edit || runCatching { org.json.JSONObject(input) }.isSuccess - Column(Modifier.fillMaxWidth().padding(12.dp)) { - Text(request.source.ifBlank { request.action }) - Text(request.action) - Text(request.resources.joinToString("\n")) + Column(Modifier.fillMaxWidth() + .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(MobileDesignGeometry.ApprovalCardRadius)) + .border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(MobileDesignGeometry.ApprovalCardRadius)) + .padding(MobileDesignGeometry.ApprovalCardPadding), + verticalArrangement = Arrangement.spacedBy(MobileDesignGeometry.ApprovalCardGap)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Image(painterResource(R.drawable.approval_shield), contentDescription = null, modifier = Modifier.size(16.dp)) + Text(request.source.ifBlank { request.action }, modifier = Modifier.weight(1f), style = MobileDesignTypography.BodySmall) + TextButton(onClick = { edit = !edit }, enabled = !state.busy, + modifier = Modifier.height(32.dp), contentPadding = PaddingValues(horizontal = 8.dp)) { + Text(stringResource(if (edit) R.string.tool_hide_approval_input else R.string.tool_edit_approval_input), style = MobileDesignTypography.LabelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + if (request.source.isNotBlank() && !request.source.equals(request.action, ignoreCase = true)) { + Text(request.action, style = MobileDesignTypography.BodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + if (request.resources.isNotEmpty()) { + Text(request.resources.joinToString("\n"), style = MobileDesignTypography.LabelSmall.copy(fontFamily = FontFamily.Monospace), + modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(8.dp)).padding(10.dp)) + } if (edit) androidx.compose.material3.OutlinedTextField(value = input, onValueChange = { input = it }, enabled = !state.busy, - isError = !valid, modifier = Modifier.fillMaxWidth()) - androidx.compose.foundation.layout.Row { - androidx.compose.material3.TextButton(onClick = { edit = !edit }, enabled = !state.busy) { Text(stringResource(R.string.tool_edit_approval)) } - androidx.compose.material3.TextButton(onClick = { onIntent(RemoteSessionIntent.RespondPermission(request.requestId, true, if (edit) input else null)) }, enabled = !state.busy && valid) { Text(stringResource(R.string.tool_approve)) } - androidx.compose.material3.TextButton(onClick = { onIntent(RemoteSessionIntent.RespondPermission(request.requestId, false, null)) }, enabled = !state.busy) { Text(stringResource(R.string.tool_reject)) } + isError = !valid, modifier = Modifier.fillMaxWidth().heightIn(max = 120.dp), textStyle = MobileDesignTypography.LabelSmall.copy(fontFamily = FontFamily.Monospace)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End)) { + Button(onClick = { onIntent(RemoteSessionIntent.RespondPermission(request.requestId, false, null)) }, enabled = !state.busy, + shape = RoundedCornerShape(MobileDesignGeometry.ApprovalActionRadius), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceVariant, contentColor = MaterialTheme.colorScheme.onSurface), + modifier = Modifier.height(MobileDesignGeometry.ApprovalActionHeight)) { Text(stringResource(R.string.tool_reject), style = MobileDesignTypography.LabelMedium) } + Button(onClick = { onIntent(RemoteSessionIntent.RespondPermission(request.requestId, true, if (edit) input else null)) }, enabled = !state.busy && valid, + shape = RoundedCornerShape(MobileDesignGeometry.ApprovalActionRadius), modifier = Modifier.height(MobileDesignGeometry.ApprovalActionHeight)) { + Text(stringResource(R.string.tool_approve), style = MobileDesignTypography.LabelMedium) + } } } } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/MarkdownContent.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/MarkdownContent.kt index ee29320f8b..ab5ee71f2c 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/MarkdownContent.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/MarkdownContent.kt @@ -29,9 +29,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle @@ -211,11 +213,20 @@ private fun annotatedInlines( } /** - * The markers live in their own 20dp column so wrapped lines line up under the + * The markers share a measured column so wrapped lines line up under the * text rather than under the bullet. */ @Composable private fun MarkdownList(items: List, onOpenLink: (String, String) -> Unit) { + val measurer = rememberTextMeasurer() + val density = LocalDensity.current + val style = MaterialTheme.typography.bodyLarge + val markerWidth = remember(items, style, density, measurer) { + with(density) { + items.maxOfOrNull { measurer.measure(it.marker, style, softWrap = false).size.width } + ?.toDp()?.coerceAtLeast(20.dp) ?: 20.dp + } + } Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp), @@ -232,7 +243,9 @@ private fun MarkdownList(items: List, onOpenLink: (String, Str lineHeight = MaterialTheme.typography.bodyLarge.lineHeight, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End, - modifier = Modifier.width(20.dp), + modifier = Modifier.width(markerWidth), + softWrap = false, + maxLines = 1, ) Box(Modifier.weight(1f)) { InlineText( diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanels.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanels.kt index 2769a6bdbb..d5809004f7 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanels.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanels.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -22,30 +23,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.openbitfun.mobile.app.R +import com.openbitfun.mobile.app.ui.theme.generated.MobileDesignGeometry import com.openbitfun.mobile.core.feature.session.QuestionAnswer import com.openbitfun.mobile.core.feature.session.QuestionAnswerValue import com.openbitfun.mobile.core.feature.session.QuestionOption -import com.openbitfun.mobile.core.feature.session.ToolApprovalEditContract -import com.openbitfun.mobile.core.feature.session.ToolApprovalEditSupport import com.openbitfun.mobile.core.feature.session.ToolQuestion -/** - * Approve and reject as equal halves of one row, ported from - * `ToolConfirmationPanel` in `pages/components/ToolInteractionPanels.ets`. - * - * Editable approval is gated by [ToolApprovalEditContract.support] and is - * intentionally not rendered while support is [ToolApprovalEditSupport.UNSUPPORTED]. - * The HarmonyOS source offers a JSON editor over `tool_input` before approving. - * Android does not expose it yet because the shared intent still carries only a - * tool id; the HarmonyOS command factory currently drops `updatedInput` at the - * wire as well. The UI and protocol need to move together before this surface - * can promise editable approval on both clients. - */ +/** Compact approval actions; rejection remains available while JSON edits are invalid. */ @Composable internal fun ToolConfirmationPanel( canApprove: Boolean, @@ -55,36 +45,38 @@ internal fun ToolConfirmationPanel( onApprove: (String?) -> Unit, onReject: () -> Unit, ) { - var editing by remember(input) { mutableStateOf(false) } - var editedInput by remember(input) { mutableStateOf(input.ifBlank { "{}" }) } + var editing by rememberSaveable(input) { mutableStateOf(false) } + var editedInput by rememberSaveable(input) { mutableStateOf(input.ifBlank { "{}" }) } val valid = !editing || runCatching { org.json.JSONObject(editedInput) }.isSuccess - if (canApprove) { - TextButton(onClick = { editing = !editing }, enabled = enabled) { Text(stringResource(R.string.tool_edit_approval)) } - if (editing) OutlinedTextField(value = editedInput, onValueChange = { editedInput = it }, isError = !valid, label = { Text(stringResource(R.string.tool_input)) }, modifier = Modifier.fillMaxWidth()) - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { + Column(verticalArrangement = Arrangement.spacedBy(MobileDesignGeometry.ApprovalCardGap)) { if (canApprove) { - PillButton( - label = stringResource(R.string.tool_approve), - primary = true, - enabled = enabled && valid, - compact = false, - onClick = { if (valid) onApprove(if (editing) editedInput else null) }, - modifier = Modifier.weight(1f), - ) + TextButton(onClick = { editing = !editing }, enabled = enabled) { Text(stringResource(if (editing) R.string.tool_hide_approval_input else R.string.tool_edit_approval_input)) } + if (editing) OutlinedTextField(enabled = enabled, value = editedInput, onValueChange = { editedInput = it }, isError = !valid, label = { Text(stringResource(R.string.tool_input)) }, modifier = Modifier.fillMaxWidth()) } - if (canReject) { - PillButton( - label = stringResource(R.string.tool_reject), - primary = false, - enabled = enabled, - compact = false, - onClick = onReject, - modifier = Modifier.weight(1f), - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(MobileDesignGeometry.ApprovalCardGap, androidx.compose.ui.Alignment.End), + ) { + if (canReject) { + Button( + onClick = onReject, + enabled = enabled, + shape = RoundedCornerShape(MobileDesignGeometry.ApprovalActionRadius), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = MaterialTheme.colorScheme.onSurface), + contentPadding = PaddingValues(horizontal = 16.dp), + modifier = Modifier.height(MobileDesignGeometry.ApprovalActionHeight), + ) { Text(stringResource(R.string.tool_reject)) } + } + if (canApprove) { + Button( + onClick = { if (valid) onApprove(if (editing) editedInput else null) }, + enabled = enabled && valid, + shape = RoundedCornerShape(MobileDesignGeometry.ApprovalActionRadius), + contentPadding = PaddingValues(horizontal = 16.dp), + modifier = Modifier.height(MobileDesignGeometry.ApprovalActionHeight), + ) { Text(stringResource(R.string.tool_approve)) } + } } } } @@ -130,7 +122,7 @@ internal fun ToolQuestionAnswerPanel( compact = false, onClick = { onSubmit(answer.trim()) - answer = "" + // Keep the draft until the authoritative question leaves the UI. }, modifier = Modifier.fillMaxWidth(), ) diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolStatusList.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolStatusList.kt index 5abb1a7a89..293ec6cac3 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolStatusList.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolStatusList.kt @@ -53,6 +53,7 @@ internal class ToolDisclosure(selectedState: MutableState) { var selected by selectedState } internal val LocalToolDisclosure = staticCompositionLocalOf { null } +internal val LocalPermissionMailbox = staticCompositionLocalOf { null } private const val REJECT_REASON = "Rejected from the Android client" private const val CANCEL_REASON = "Cancelled from the Android client" @@ -218,7 +219,11 @@ internal fun ToolStatusRow( if (disclosure == null) localExpanded = !localExpanded else disclosure.selected = if (expanded) null else tool.id } - val blocking = tool.actions.isNotEmpty() + val transcriptActions = if (LocalPermissionMailbox.current?.ownsToolInteraction(tool.id) == true) { + tool.actions - setOf(ToolAction.ANSWER, ToolAction.APPROVE, ToolAction.REJECT) - + (if (ToolAction.ANSWER in tool.actions) setOf(ToolAction.CANCEL) else emptySet()) + } else tool.actions + val blocking = transcriptActions.isNotEmpty() val emphasized = expanded || blocking || tool.phase == ToolPhase.FAILED val canExpand = tool.expandable val openable = tool.filePath.isNotEmpty() @@ -305,18 +310,18 @@ internal fun ToolStatusRow( } } - if (ToolAction.APPROVE in tool.actions || ToolAction.REJECT in tool.actions) { + if (ToolAction.APPROVE in transcriptActions || ToolAction.REJECT in transcriptActions) { ToolConfirmationPanel( input = tool.input, - canApprove = ToolAction.APPROVE in tool.actions, - canReject = ToolAction.REJECT in tool.actions, + canApprove = ToolAction.APPROVE in transcriptActions, + canReject = ToolAction.REJECT in transcriptActions, enabled = enabled, onApprove = onApprove, onReject = { onReject(REJECT_REASON) }, ) } - if (ToolAction.ANSWER in tool.actions) { + if (ToolAction.ANSWER in transcriptActions) { if (tool.questions.isNotEmpty()) { ToolStructuredQuestionPanel( toolId = tool.id, @@ -335,7 +340,7 @@ internal fun ToolStatusRow( } } - if (ToolAction.CANCEL in tool.actions) { + if (ToolAction.CANCEL in transcriptActions) { Row( modifier = Modifier.fillMaxWidth().padding(start = DETAIL_INDENT), horizontalArrangement = Arrangement.End, diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/CreateSessionScreen.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/CreateSessionScreen.kt index b4c44a92cd..917b79b64e 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/CreateSessionScreen.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/CreateSessionScreen.kt @@ -155,6 +155,7 @@ internal fun CreateSessionScreen( var draft by rememberSaveable { mutableStateOf("") } var workspacePath by rememberSaveable { mutableStateOf("") } var workspaceConnectionId by rememberSaveable { mutableStateOf(null) } + var workspaceSshHost by rememberSaveable { mutableStateOf(null) } var selectedModelId by rememberSaveable { mutableStateOf(null) } // Not saveable: an open sheet is a finger part-way through a gesture. var pickerKind by remember { mutableStateOf(null) } @@ -173,6 +174,7 @@ internal fun CreateSessionScreen( val selectWorkspace: (WorkspaceChoice?) -> Unit = { workspace -> workspacePath = workspace?.path.orEmpty() workspaceConnectionId = workspace?.remoteConnectionId + workspaceSshHost = workspace?.remoteSshHost } val voiceInput = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> @@ -281,9 +283,11 @@ internal fun CreateSessionScreen( ) { WorkspacePicker( workspaces = ready?.workspaces.orEmpty().map { - WorkspaceChoice(path = it.path, name = it.displayName, remoteConnectionId = it.remoteConnectionId) + WorkspaceChoice(path = it.path, name = it.displayName, remoteConnectionId = it.remoteConnectionId, remoteSshHost = it.remoteSshHost) }, selectedPath = workspacePath, + selectedConnectionId = workspaceConnectionId, + selectedSshHost = workspaceSshHost, showHeader = false, onDismiss = { pickerKind = null }, onPick = { path -> @@ -350,6 +354,7 @@ internal fun CreateSessionScreen( modelId = selectedModelId, workspacePath = workspacePath.takeIf { it.isNotBlank() }, remoteConnectionId = workspaceConnectionId, + remoteSshHost = workspaceSshHost, ), ) draft = "" @@ -364,9 +369,11 @@ internal fun CreateSessionScreen( ModalBottomSheet(onDismissRequest = { pickerKind = null }) { WorkspacePicker( workspaces = ready?.workspaces.orEmpty().map { - WorkspaceChoice(path = it.path, name = it.displayName, remoteConnectionId = it.remoteConnectionId) + WorkspaceChoice(path = it.path, name = it.displayName, remoteConnectionId = it.remoteConnectionId, remoteSshHost = it.remoteSshHost) }, selectedPath = workspacePath, + selectedConnectionId = workspaceConnectionId, + selectedSshHost = workspaceSshHost, showHeader = true, onDismiss = { pickerKind = null }, onPick = { path -> @@ -389,7 +396,7 @@ private const val ASSISTANT_KIND = "assistant" * The workspace domain type carries a kind and a timestamp the picker has no use * for, and the app layer cannot see `core-domain` anyway. */ -private data class WorkspaceChoice(val path: String, val name: String, val remoteConnectionId: String?) +private data class WorkspaceChoice(val path: String, val name: String, val remoteConnectionId: String?, val remoteSshHost: String?) /** * The tap target that puts the keyboard away. @@ -487,6 +494,8 @@ private fun DevicePicker( private fun WorkspacePicker( workspaces: List, selectedPath: String, + selectedConnectionId: String?, + selectedSshHost: String?, showHeader: Boolean, onDismiss: () -> Unit, onPick: (WorkspaceChoice?) -> Unit, @@ -534,7 +543,8 @@ private fun WorkspacePicker( // Two projects can share a name; the path is what tells them // apart, and it is the only place the user can check. subtitle = workspace.path, - selected = workspace.path == selectedPath, + selected = workspace.path == selectedPath && workspace.remoteConnectionId == selectedConnectionId && + workspace.remoteSshHost == selectedSshHost, onClick = { onPick(workspace) }, ) } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DeviceToolsDialog.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DeviceToolsDialog.kt new file mode 100644 index 0000000000..d1e7df117c --- /dev/null +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DeviceToolsDialog.kt @@ -0,0 +1,87 @@ +package com.openbitfun.mobile.app.ui.remote + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.openbitfun.mobile.app.R +import com.openbitfun.mobile.core.feature.workspace.* + +/** Native navigation only. Location, selected panel and PTY lifetime belong to KMP. */ +@Composable +internal fun DeviceToolsDialog(state: RemoteWorkspaceUiState.Ready, onIntent: (RemoteWorkspaceIntent) -> Unit) { + val tools = state.deviceTools + var choosing by remember { mutableStateOf(false) } + val panels = rememberSaveableStateHolder() + RuntimeFullScreen(stringResource(R.string.device_tools), { onIntent(RemoteWorkspaceIntent.CloseDeviceTools) }) { + Box { + TextButton(enabled = !tools.busy && state.files.file == null, onClick = { choosing = true }, + colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface)) { + Text(state.savedConnections.firstOrNull { it.id == tools.connectionId }?.name ?: stringResource(R.string.workspace_target_device)) + } + DropdownMenu(expanded = choosing, onDismissRequest = { choosing = false }) { + DropdownMenuItem(text = { Text(stringResource(R.string.workspace_target_device)) }, onClick = { + choosing = false; onIntent(RemoteWorkspaceIntent.OpenDeviceTools()) + }) + state.savedConnections.forEach { connection -> + DropdownMenuItem(text = { Text(connection.name) }, onClick = { + choosing = false; onIntent(RemoteWorkspaceIntent.OpenDeviceTools(connectionId = connection.id)) + }) + } + } + } + TabRow( + selectedTabIndex = tools.panel.ordinal, + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface, + indicator = { positions -> + TabRowDefaults.SecondaryIndicator( + modifier = Modifier.tabIndicatorOffset(positions[tools.panel.ordinal]), + color = MaterialTheme.colorScheme.onSurface, + ) + }, + ) { + DeviceToolsPanel.entries.forEach { panel -> + Tab( + selected = panel == tools.panel, + enabled = state.files.file == null, + onClick = { onIntent(RemoteWorkspaceIntent.SelectDeviceToolsPanel(panel)) }, + selectedContentColor = MaterialTheme.colorScheme.onSurface, + unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant, + text = { Text(stringResource(if (panel == DeviceToolsPanel.FILES) R.string.workspace_browse_files else R.string.workspace_terminal)) }, + ) + } + } + when { + tools.busy -> CircularProgressIndicator(Modifier.padding(16.dp)) + tools.failed -> TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { onIntent(RemoteWorkspaceIntent.OpenDeviceTools(connectionId = tools.connectionId)) }) { + Text(stringResource(R.string.workspace_files_failed)) + } + else -> panels.SaveableStateProvider("${tools.connectionId}:${tools.path}:${tools.panel}") { + when (tools.panel) { + DeviceToolsPanel.FILES -> RuntimeFilesContent(state, onIntent) + DeviceToolsPanel.TERMINAL -> { + val terminal = state.terminal + if (terminal.failed) Text(terminal.errorDetail ?: stringResource(R.string.workspace_terminal_failed), color = MaterialTheme.colorScheme.error) + if (terminal.busy) CircularProgressIndicator(Modifier.padding(12.dp)) + if (terminal.sessionId != null) { + Row { + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !terminal.busy, onClick = { onIntent(RemoteWorkspaceIntent.WriteTerminal("\u0003")) }) { Text(stringResource(R.string.message_stop)) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !terminal.busy, onClick = { onIntent(RemoteWorkspaceIntent.CloseTerminal) }) { Text(stringResource(R.string.workspace_terminal_close)) } + } + RuntimeTerminalView(terminal, { onIntent(RemoteWorkspaceIntent.WriteTerminal(it)) }, { cols, rows -> onIntent(RemoteWorkspaceIntent.ResizeTerminal(cols, rows)) }, Modifier.fillMaxWidth().weight(1f)) + } else if (!terminal.busy) { + Box(Modifier.fillMaxWidth().weight(1f), contentAlignment = androidx.compose.ui.Alignment.Center) { + OutlinedButton(colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { onIntent(RemoteWorkspaceIntent.StartDeviceToolsTerminal) }) { Text(stringResource(R.string.workspace_open_terminal)) } + } + } + } + } + } + } + } +} diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicy.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicy.kt new file mode 100644 index 0000000000..12ce81e778 --- /dev/null +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicy.kt @@ -0,0 +1,6 @@ +package com.openbitfun.mobile.app.ui.remote + +/** Preserve the host filename when Android associates its extension with another MIME type. */ +internal fun documentExportMimeType(declared: String, extensionMimeType: String?): String = + if (declared.isNotBlank() && declared.equals(extensionMimeType, ignoreCase = true)) declared + else "application/octet-stream" diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/PairingScreen.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/PairingScreen.kt index b7cefe7ffc..fd2ba8436d 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/PairingScreen.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/PairingScreen.kt @@ -7,7 +7,8 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.HorizontalDivider import com.openbitfun.mobile.app.ui.shell.WelcomeBrandFlow import com.openbitfun.mobile.app.ui.theme.generated.MobileDesignGeometry -import com.openbitfun.mobile.core.feature.session.SessionTimePresentation +import com.openbitfun.mobile.core.feature.session.RecentSessionsPresentation +import com.openbitfun.mobile.core.feature.session.RecentSessionUiState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -22,6 +23,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -132,7 +134,6 @@ private fun RemoteConnectedScreen( connectionDetails: @Composable () -> Unit, modifier: Modifier, ) { - RemoteDownloadSaver(workspaceState, onWorkspaceIntent) val ready = remoteState as? RemoteSessionUiState.Ready // Route immediately. A previous session snapshot must never stand in for the requested one. val conversation = requestedSessionId?.takeIf { remoteState !is RemoteSessionUiState.Failed }?.let { requested -> @@ -221,7 +222,6 @@ private fun RemoteConnectedScreen( onOpenSession(id) onSessionIntent(com.openbitfun.mobile.core.feature.session.RemoteSessionIntent.Open(id)) }, - onOpenRemoteSettings = onOpenRemoteSettings, modifier = modifier, ) } @@ -234,16 +234,15 @@ internal fun RemoteCompactHome( onOpenSidebar: (() -> Unit)?, onBrowse: (() -> Unit)?, onOpen: (String) -> Unit, - onOpenRemoteSettings: () -> Unit, modifier: Modifier = Modifier, ) { val ready = remoteState as? RemoteSessionUiState.Ready - val recent = ready?.sessions.orEmpty().filter { it.status != "archived" }.sortedByDescending { - SessionTimePresentation.timestampMs(it.updatedAt)?.takeIf { t -> t > 0 } - ?: SessionTimePresentation.timestampMs(it.createdAt) ?: 0 - }.take(3) + val sessions = ready?.sessions.orEmpty() + val recent = RecentSessionsPresentation.sessionIds(sessions.map { + RecentSessionUiState(it.id, it.status, it.updatedAt, it.createdAt) + }).mapNotNull { id -> sessions.firstOrNull { it.id == id } } Column(modifier.fillMaxSize()) { - RemoteShellHeader(onOpenSidebar, desktopName, onOpenRemoteSettings) + RemoteShellHeader(onOpenSidebar, desktopName, null) Column( Modifier.weight(1f).align(Alignment.CenterHorizontally) .widthIn(max = MobileDesignGeometry.RecentHomeMaxWidth).fillMaxWidth() @@ -261,7 +260,10 @@ internal fun RemoteCompactHome( Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Text(stringResource(R.string.home_recent_recent), fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.weight(1f)) - if (onBrowse != null) TextButton(onClick = onBrowse) { Text(stringResource(R.string.home_recent_all), fontSize = 12.sp) } + if (onBrowse != null) TextButton( + onClick = onBrowse, + colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), + ) { Text(stringResource(R.string.home_recent_all), fontSize = 12.sp) } } recent.forEach { session -> Column(Modifier.fillMaxWidth().clickable(enabled = !ready!!.busy) { onOpen(session.id) } @@ -313,7 +315,7 @@ private fun RemoteShellHeader( verticalArrangement = Arrangement.spacedBy(3.dp), ) { Text( - stringResource(R.string.navigation_remote), + stringResource(R.string.app_name), fontSize = if (hasSubtitle) 18.sp else 17.sp, fontWeight = FontWeight.Medium, maxLines = 1, diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteDownloadSaver.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteDownloadSaver.kt index 0989cc3917..9349ebb690 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteDownloadSaver.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteDownloadSaver.kt @@ -62,13 +62,19 @@ internal fun RemoteDownloadSaver( } val awaiting = (state as? RemoteWorkspaceUiState.Ready)?.download as? RemoteFileDownloadUiState.AwaitingSave - LaunchedEffect(awaiting?.target?.controlTargetEpoch) { + LaunchedEffect(awaiting?.localReference) { if (awaiting == null || pending != null) return@LaunchedEffect pending = awaiting launcher.launch( Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - type = awaiting.mimeType.ifBlank { "application/octet-stream" } + // DocumentsUI appends its MIME extension when it disagrees with + // the supplied filename (for example source.ts -> source.ts.txt). + val extension = awaiting.name.substringAfterLast('.', "").lowercase() + type = documentExportMimeType( + awaiting.mimeType, + android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension), + ) putExtra(Intent.EXTRA_TITLE, awaiting.name) }, ) diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteSessionListView.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteSessionListView.kt index cdcad1231e..6f13eca7b1 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteSessionListView.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RemoteSessionListView.kt @@ -268,7 +268,8 @@ internal fun RemoteSessionListContent( val matching = scope?.workspaces.orEmpty().filter { it.path == section.path } val selected = scope?.selected?.takeIf { it.path == section.path } if (selected != null || matching.size == 1) { - onIntent(RemoteSessionIntent.CreateSession(agentType, "", "", null, section.path, selected?.remoteConnectionId ?: matching.singleOrNull()?.remoteConnectionId)) + onIntent(RemoteSessionIntent.CreateSession(agentType, "", "", null, section.path, (if (selected != null) selected.remoteConnectionId else matching.singleOrNull()?.remoteConnectionId), + (if (selected != null) selected.remoteSshHost else matching.singleOrNull()?.remoteSshHost))) } else onCreate() } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeTerminalView.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeTerminalView.kt index a7f0306923..f6f8c5e9d6 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeTerminalView.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeTerminalView.kt @@ -6,6 +6,8 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.compose.runtime.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView import com.openbitfun.mobile.core.feature.workspace.RuntimeTerminalUiState @@ -15,15 +17,31 @@ import org.json.JSONObject @SuppressLint("SetJavaScriptEnabled") @Composable internal fun RuntimeTerminalView(state: RuntimeTerminalUiState, onInput: (String) -> Unit, onResize: (Int, Int) -> Unit, modifier: Modifier) { + val background = MaterialTheme.colorScheme.background.toArgb() + val foreground = MaterialTheme.colorScheme.onBackground.toArgb() val latestInput by rememberUpdatedState(onInput) val latestResize by rememberUpdatedState(onResize) var bridge by remember { mutableStateOf(null) } AndroidView(modifier = modifier, factory = { context -> WebView(context).apply { + if (context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE != 0) WebView.setWebContentsDebuggingEnabled(true) + // Without explicit match-parent params WebView can keep a zero CSS + // viewport even after Compose measures the native view to full height. + layoutParams = android.view.ViewGroup.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + ) settings.javaScriptEnabled = true settings.allowFileAccess = true settings.allowContentAccess = false isFocusableInTouchMode = true + // DOM focus alone does not move focus out of the Compose dialog. + // Give WebView native focus before xterm focuses its textarea so + // Android establishes the input connection and routes keys here. + setOnTouchListener { view, event -> + if (event.actionMasked == android.view.MotionEvent.ACTION_DOWN) view.requestFocus() + false + } val adapter = TerminalBridge(this, { latestInput(it) }, { cols, rows -> latestResize(cols, rows) }) bridge = adapter addJavascriptInterface(adapter, "OpenBitFunTerminalHost") @@ -33,11 +51,23 @@ internal fun RuntimeTerminalView(state: RuntimeTerminalUiState, onInput: (String } loadUrl("file:///android_asset/index.html") } - }, update = { bridge?.update(state) }) + }, update = { bridge?.updateTheme(background, foreground); bridge?.update(state) }) DisposableEffect(Unit) { onDispose { bridge?.dispose() } } } private class TerminalBridge(private val view: WebView, private val input: (String) -> Unit, private val resize: (Int, Int) -> Unit) { + private var theme: String? = null + fun updateTheme(background: Int, foreground: Int) { + fun css(color: Int) = "#" + (color and 0xffffff).toString(16).padStart(6, '0') + val next = JSONObject().put("background", css(background)).put("foreground", css(foreground)).put("cursor", css(foreground)).toString() + view.setBackgroundColor(background) + if (next == theme) return + theme = next + renderTheme() + } + private fun renderTheme() { + if (ready && !disposed) theme?.let { view.evaluateJavascript("window.OpenBitFunTerminal.setTheme($it)", null) } + } private var state: RuntimeTerminalUiState? = null private var ready = false private var disposed = false @@ -58,7 +88,7 @@ private class TerminalBridge(private val view: WebView, private val input: (Stri if (disposed) return@post val event = runCatching { JSONObject(message) }.getOrNull() ?: return@post when (event.optString("type")) { - "ready" -> { ready = true; render(true) } + "ready" -> { ready = true; renderTheme(); render(true) } "resync" -> render(true) "input" -> input(event.optString("data")) "resize" -> { val cols = event.optInt("cols"); val rows = event.optInt("rows"); if (cols > 0 && rows > 0) resize(cols, rows) } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspaceDialogs.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspaceDialogs.kt index a9921bacad..7f2318b1fb 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspaceDialogs.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspaceDialogs.kt @@ -1,6 +1,13 @@ package com.openbitfun.mobile.app.ui.remote import androidx.compose.foundation.layout.* +import androidx.compose.foundation.clickable +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.Alignment +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.horizontalScroll @@ -16,16 +23,17 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import com.openbitfun.mobile.app.ui.chat.statusText import com.openbitfun.mobile.app.R import com.openbitfun.mobile.core.feature.workspace.* @Composable -private fun RuntimeFullScreen(title: String, onBack: () -> Unit, actions: @Composable RowScope.() -> Unit = {}, content: @Composable ColumnScope.() -> Unit) { +internal fun RuntimeFullScreen(title: String, onBack: () -> Unit, actions: @Composable RowScope.() -> Unit = {}, content: @Composable ColumnScope.() -> Unit) { Dialog(onDismissRequest = onBack, properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false)) { Surface(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize().safeDrawingPadding().imePadding()) { Row(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - TextButton(onClick = onBack) { Text(stringResource(R.string.common_back)) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = onBack) { Text(stringResource(R.string.common_back)) } Text(title, Modifier.weight(1f).padding(vertical = 12.dp), style = MaterialTheme.typography.titleMedium, maxLines = 1) actions() } @@ -40,25 +48,32 @@ private fun RuntimeFullScreen(title: String, onBack: () -> Unit, actions: @Compo internal fun RuntimeFileEditorDialog(files: RuntimeFilesUiState, onIntent: (RemoteWorkspaceIntent) -> Unit) { val file = files.file ?: return var content by rememberSaveable(file) { mutableStateOf(files.content) } + var savedContent by rememberSaveable(file) { mutableStateOf(files.content) } var discard by rememberSaveable(file) { mutableStateOf(false) } var fileAction by remember { mutableStateOf("") } var renamePath by remember(file) { mutableStateOf(file) } var menu by remember { mutableStateOf(false) } - LaunchedEffect(files.content) { content = files.content } + LaunchedEffect(files.content) { + // Recreation restores the draft; only a new host revision replaces it. + if (files.content != savedContent) { + content = files.content + savedContent = files.content + } + } val dirty = content != files.content val back = { if (!files.busy) { if (dirty) discard = true else onIntent(RemoteWorkspaceIntent.CloseFileEditor) } } RuntimeFullScreen(file.substringAfterLast('/'), back, actions = { Box { - TextButton(enabled = !files.busy && !dirty, onClick = { menu = true }) { Text("โ‹ฏ") } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !files.busy && !dirty, onClick = { menu = true }) { Text("โ‹ฏ") } DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) { DropdownMenuItem(text = { Text(stringResource(R.string.workspace_rename_file)) }, onClick = { menu = false; fileAction = "rename" }) DropdownMenuItem(text = { Text(stringResource(R.string.workspace_delete_file)) }, onClick = { menu = false; fileAction = "delete" }) } } - TextButton(enabled = !files.busy && dirty, onClick = { onIntent(RemoteWorkspaceIntent.SaveFile(content)) }) { Text(stringResource(R.string.workspace_save_file)) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !files.busy && dirty, onClick = { onIntent(RemoteWorkspaceIntent.SaveFile(content)) }) { Text(stringResource(R.string.workspace_save_file)) } }) { Text(file, Modifier.padding(horizontal = 16.dp, vertical = 8.dp), style = MaterialTheme.typography.bodySmall) - if (files.failed) Text(stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(16.dp)) + if (files.failed) Text(if (files.saveConflict) stringResource(R.string.workspace_file_conflict) else files.errorDetail ?: stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(16.dp)) BoxWithConstraints(Modifier.fillMaxWidth().weight(1f)) { val viewportWidth = maxWidth - 60.dp val codeStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace, fontSize = 14.sp, lineHeight = 21.sp, platformStyle = PlatformTextStyle(includeFontPadding = false)) @@ -75,30 +90,30 @@ internal fun RuntimeFileEditorDialog(files: RuntimeFilesUiState, onIntent: (Remo if (fileAction.isNotEmpty()) AlertDialog(onDismissRequest = { fileAction = "" }, title = { Text(stringResource(if (fileAction == "rename") R.string.workspace_rename_file else R.string.workspace_delete_file)) }, text = { if (fileAction == "rename") OutlinedTextField(value = renamePath, onValueChange = { renamePath = it }, singleLine = true) else Text(file) }, - confirmButton = { TextButton(enabled = fileAction != "rename" || renamePath.isNotBlank(), onClick = { + confirmButton = { TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = fileAction != "rename" || renamePath.isNotBlank(), onClick = { if (fileAction == "rename") onIntent(RemoteWorkspaceIntent.RenameFile(renamePath)) else onIntent(RemoteWorkspaceIntent.DeleteFile) fileAction = "" }) { Text(stringResource(if (fileAction == "rename") R.string.workspace_rename_file else R.string.workspace_delete_file)) } }, - dismissButton = { TextButton(onClick = { fileAction = "" }) { Text(stringResource(R.string.common_cancel)) } }) + dismissButton = { TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { fileAction = "" }) { Text(stringResource(R.string.common_cancel)) } }) if (discard) AlertDialog(onDismissRequest = { discard = false }, title = { Text(stringResource(R.string.workspace_discard_changes)) }, - confirmButton = { TextButton(onClick = { discard = false; onIntent(RemoteWorkspaceIntent.CloseFileEditor) }) { Text(stringResource(R.string.workspace_discard)) } }, - dismissButton = { TextButton(onClick = { discard = false }) { Text(stringResource(R.string.common_cancel)) } }) + confirmButton = { TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { discard = false; onIntent(RemoteWorkspaceIntent.CloseFileEditor) }) { Text(stringResource(R.string.workspace_discard)) } }, + dismissButton = { TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { discard = false }) { Text(stringResource(R.string.common_cancel)) } }) } @Composable internal fun RuntimeDirectoryPickerDialog(state: RuntimeFilesUiState, connectionId: String?, onIntent: (RemoteWorkspaceIntent) -> Unit, onChoose: (String) -> Unit, onBack: () -> Unit) { RuntimeFullScreen(stringResource(R.string.workspace_choose_folder), onBack, actions = { - TextButton(enabled = !state.busy && !state.failed && state.directory.isNotBlank(), onClick = { onChoose(state.directory) }) { Text(stringResource(R.string.workspace_choose)) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !state.busy && !state.failed && state.directory.isNotBlank(), onClick = { onChoose(state.directory) }) { Text(stringResource(R.string.workspace_choose)) } }) { Text(state.directory, Modifier.padding(16.dp), style = MaterialTheme.typography.bodySmall) if (state.busy) LinearProgressIndicator(Modifier.fillMaxWidth()) - if (state.failed) Text(stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(16.dp)) + if (state.failed) Text(state.errorDetail ?: stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(16.dp)) Column(Modifier.fillMaxWidth().weight(1f).verticalScroll(rememberScrollState()).padding(horizontal = 16.dp)) { - TextButton(enabled = !state.busy && state.directory != "/", onClick = { onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(state.directory.trimEnd('/').substringBeforeLast('/', "").ifEmpty { "/" }, connectionId, false)) }) { Text(stringResource(R.string.workspace_parent_folder)) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !state.busy && state.directory != "/", onClick = { onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(state.directory.trimEnd('/').substringBeforeLast('/', "").ifEmpty { "/" }, connectionId, false)) }) { Text(stringResource(R.string.workspace_parent_folder)) } state.entries.filter { it.directory }.forEach { entry -> - TextButton(onClick = { onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(entry.path, connectionId, false)) }, enabled = !state.busy, modifier = Modifier.fillMaxWidth()) { Text(entry.name) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(entry.path, connectionId, false)) }, enabled = !state.busy, modifier = Modifier.fillMaxWidth()) { Text(entry.name) } } - if (state.hasMore) TextButton(onClick = { onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(state.directory, connectionId, true)) }, enabled = !state.busy) { Text(stringResource(R.string.workspace_more_files)) } + if (state.hasMore) TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(state.directory, connectionId, true)) }, enabled = !state.busy) { Text(stringResource(R.string.workspace_more_files)) } } } } @@ -106,19 +121,32 @@ internal fun RuntimeDirectoryPickerDialog(state: RuntimeFilesUiState, connection @Composable internal fun RuntimeTerminalDialog(state: RuntimeTerminalUiState, onIntent: (RemoteWorkspaceIntent) -> Unit, onBack: () -> Unit) { RuntimeFullScreen(stringResource(R.string.workspace_terminal), onBack, actions = { - TextButton(enabled = !state.busy, onClick = { onIntent(RemoteWorkspaceIntent.CloseTerminal) }) { Text(stringResource(R.string.workspace_terminal_close)) } + if (state.sessionId != null) { + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !state.busy, onClick = { onIntent(RemoteWorkspaceIntent.WriteTerminal("\u0003")) }) { Text(stringResource(R.string.message_stop)) } + TextButton(colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), enabled = !state.busy, onClick = { onIntent(RemoteWorkspaceIntent.CloseTerminal) }) { Text(stringResource(R.string.workspace_terminal_close)) } + } }) { - if (state.failed) Text(stringResource(R.string.workspace_terminal_failed), color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(12.dp)) - RuntimeTerminalView(state, { onIntent(RemoteWorkspaceIntent.WriteTerminal(it)) }, { cols, rows -> onIntent(RemoteWorkspaceIntent.ResizeTerminal(cols, rows)) }, Modifier.fillMaxWidth().weight(1f)) + if (state.failed) Text(state.errorDetail ?: stringResource(R.string.workspace_terminal_failed), color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(12.dp)) + if (state.busy) CircularProgressIndicator(Modifier.padding(12.dp)) + if (state.sessionId != null) { + RuntimeTerminalView(state, { onIntent(RemoteWorkspaceIntent.WriteTerminal(it)) }, { cols, rows -> onIntent(RemoteWorkspaceIntent.ResizeTerminal(cols, rows)) }, Modifier.fillMaxWidth().weight(1f)) + } else if (!state.busy) { + Box(Modifier.fillMaxWidth().weight(1f), contentAlignment = androidx.compose.ui.Alignment.Center) { + OutlinedButton( + onClick = { onIntent(RemoteWorkspaceIntent.ReopenTerminal) }, + colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), + ) { Text(stringResource(R.string.workspace_open_terminal)) } + } + } } } @Composable -internal fun RuntimeFileSortMenu(sort: RuntimeFileSort, onIntent: (RemoteWorkspaceIntent) -> Unit) { +internal fun RuntimeFileSortMenu(sort: RuntimeFileSort, onIntent: (RemoteWorkspaceIntent) -> Unit, enabled: Boolean = true) { var open by remember { mutableStateOf(false) } val labels = listOf(R.string.workspace_sort_name_asc, R.string.workspace_sort_name_desc, R.string.workspace_sort_modified_desc, R.string.workspace_sort_modified_asc) Box { - TextButton(onClick = { open = true }) { Text(stringResource(labels[sort.ordinal])) } + TextButton(enabled = enabled, colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), onClick = { open = true }) { Text(stringResource(labels[sort.ordinal]), maxLines = 1, overflow = TextOverflow.Ellipsis) } DropdownMenu(expanded = open, onDismissRequest = { open = false }) { RuntimeFileSort.entries.forEachIndexed { index, value -> DropdownMenuItem(text = { Text(stringResource(labels[index])) }, onClick = { open = false; onIntent(RemoteWorkspaceIntent.SortFiles(value)) }) } } @@ -127,56 +155,170 @@ internal fun RuntimeFileSortMenu(sort: RuntimeFileSort, onIntent: (RemoteWorkspa @Composable internal fun RuntimeFilesDialog(state: RemoteWorkspaceUiState.Ready, onIntent: (RemoteWorkspaceIntent) -> Unit, onBack: () -> Unit) { + RuntimeFullScreen(stringResource(R.string.workspace_browse_files), onBack) { RuntimeFilesContent(state, onIntent) } +} + +private enum class FileListAction { FILE, DIRECTORY, RENAME, DELETE, UPLOAD } + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun ColumnScope.RuntimeFilesContent(state: RemoteWorkspaceUiState.Ready, onIntent: (RemoteWorkspaceIntent) -> Unit) { + // A new location/order starts at the top; pagination and refresh retain position. + val listState = remember(state.files.directory, state.files.sort) { LazyListState() } var attached by remember { mutableStateOf(true) } DisposableEffect(Unit) { onDispose { attached = false } } val uploadContext = androidx.compose.ui.platform.LocalContext.current var uploadFailed by rememberSaveable { mutableStateOf(false) } - var uploadPath by rememberSaveable { mutableStateOf("") } + var action by remember { mutableStateOf(null) } + var target by remember { mutableStateOf("") } + var name by rememberSaveable { mutableStateOf("") } + var submitted by remember { mutableStateOf(false) } + var submittedRevision by remember { mutableStateOf(0L) } + var toolbarMenu by remember { mutableStateOf(false) } + val neutral = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface) + fun begin(value: FileListAction, path: String = "", initial: String = "") { + action = value; target = path; name = initial; submitted = false; uploadFailed = false + } + LaunchedEffect(state.files.completedOperation, submitted) { + if (submitted && state.files.completedOperation > submittedRevision) { + if (!state.files.failed) action = null + submitted = false + } + } val uploadPicker = androidx.activity.compose.rememberLauncherForActivityResult(androidx.activity.result.contract.ActivityResultContracts.OpenDocument()) { uri -> if (uri != null && attached) { try { val source = com.openbitfun.mobile.app.platform.AndroidRuntimeUploadSource(uploadContext.contentResolver, uri) - onIntent(RemoteWorkspaceIntent.UploadFile(uploadPath, source)); uploadFailed = false - } catch (_: Exception) { uploadFailed = true } + submittedRevision = state.files.completedOperation; submitted = true + onIntent(RemoteWorkspaceIntent.UploadFileEntry(name, source)); uploadFailed = false + } catch (_: Exception) { submitted = false; uploadFailed = true } } } - var newDirectory by rememberSaveable { mutableStateOf("") } - RuntimeFullScreen(stringResource(R.string.workspace_browse_files), onBack) { - Column(Modifier.fillMaxWidth().weight(1f).verticalScroll(rememberScrollState()).padding(16.dp)) { - if (state.files.directory.isNotEmpty()) { - Text(state.files.directory, style = MaterialTheme.typography.bodySmall) - TextButton(enabled = !state.files.busy && state.files.directory != "/", onClick = { - onIntent(RemoteWorkspaceIntent.BrowseFiles(state.files.directory.trimEnd('/').substringBeforeLast('/', "").ifEmpty { "/" }, false)) - }) { Text(stringResource(R.string.workspace_parent_folder)) } + Column(Modifier.fillMaxWidth().padding(horizontal = 16.dp)) { + Text(state.files.directory, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(vertical = 12.dp), maxLines = 2, overflow = TextOverflow.Ellipsis) + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + IconButton(enabled = !state.files.busy && state.files.directory != "/", onClick = { + onIntent(RemoteWorkspaceIntent.BrowseFiles(state.files.directory.trimEnd('/').substringBeforeLast('/', "").ifEmpty { "/" }, false)) + }) { Icon(painterResource(R.drawable.ic_symbol_arrow_up), stringResource(R.string.workspace_parent_folder), Modifier.size(20.dp)) } + IconButton(enabled = !state.files.busy, onClick = { onIntent(RemoteWorkspaceIntent.BrowseFiles(state.files.directory, false)) }) { + Icon(painterResource(R.drawable.ic_symbol_arrow_clockwise), stringResource(R.string.common_refresh), Modifier.size(20.dp)) } - RuntimeFileSortMenu(state.files.sort, onIntent) - state.files.entries.forEach { entry -> - TextButton(onClick = { - if (entry.directory) onIntent(RemoteWorkspaceIntent.BrowseFiles(entry.path, false)) - else onIntent(RemoteWorkspaceIntent.ReadFile(entry.path)) - }, enabled = !state.files.busy) { Text(entry.name) } - if (!entry.directory) TextButton(onClick = { - onIntent(RemoteWorkspaceIntent.DownloadFile(entry.path, entry.name, "")) - }, enabled = state.download !is com.openbitfun.mobile.core.feature.workspace.RemoteFileDownloadUiState.Loading) { - Text(stringResource(R.string.file_download)) + Box(Modifier.weight(1f)) { RuntimeFileSortMenu(state.files.sort, onIntent, enabled = !state.files.busy) } + Box { + IconButton(enabled = !state.files.busy && state.files.directory.isNotEmpty(), onClick = { toolbarMenu = true }) { + Icon(painterResource(R.drawable.ic_symbol_plus), stringResource(R.string.workspace_create_file), Modifier.size(20.dp)) + } + DropdownMenu(toolbarMenu, { toolbarMenu = false }) { + listOf(FileListAction.FILE to R.string.workspace_create_file, FileListAction.DIRECTORY to R.string.workspace_create_directory, + FileListAction.UPLOAD to R.string.workspace_upload_file).forEach { (value, label) -> + DropdownMenuItem(text = { Text(stringResource(label)) }, onClick = { toolbarMenu = false; begin(value) }) + } + } + } + } + if (state.files.busy) LinearProgressIndicator(Modifier.fillMaxWidth()) + if (state.files.failed && action == null) Text(state.files.errorDetail ?: stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error) + } + LazyColumn(Modifier.fillMaxWidth().weight(1f), state = listState, contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)) { + items(state.files.entries, key = { it.path }) { entry -> + var menu by remember { mutableStateOf(false) } + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Row(Modifier.weight(1f).heightIn(min = 52.dp).clickable(enabled = !state.files.busy) { + if (entry.directory) onIntent(RemoteWorkspaceIntent.BrowseFiles(entry.path, false)) else onIntent(RemoteWorkspaceIntent.ReadFile(entry.path)) + }, verticalAlignment = Alignment.CenterVertically) { + Icon(painterResource(if (entry.directory) R.drawable.ic_symbol_folder else R.drawable.ic_symbol_doc), null, + Modifier.padding(end = 12.dp).size(22.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) + Text(entry.name, style = MaterialTheme.typography.bodyMedium, maxLines = 2, overflow = TextOverflow.Ellipsis) + } + Box { + IconButton(enabled = !state.files.busy, onClick = { menu = true }) { + Icon(painterResource(R.drawable.ic_symbol_ellipsis), stringResource(R.string.workspace_file_actions), Modifier.size(20.dp)) + } + DropdownMenu(menu, { menu = false }) { + if (!entry.directory) DropdownMenuItem(text = { Text(stringResource(R.string.file_download)) }, + enabled = state.download !is RemoteFileDownloadUiState.Loading, + onClick = { menu = false; onIntent(RemoteWorkspaceIntent.DownloadFile(entry.path, entry.name, "")) }) + DropdownMenuItem(text = { Text(stringResource(R.string.session_rename)) }, onClick = { menu = false; begin(FileListAction.RENAME, entry.path, entry.name) }) + DropdownMenuItem(text = { Text(stringResource(R.string.session_delete), color = MaterialTheme.colorScheme.error) }, onClick = { menu = false; begin(FileListAction.DELETE, entry.path, entry.name) }) + } } } - if (state.files.hasMore) TextButton(onClick = { onIntent(RemoteWorkspaceIntent.BrowseFiles(state.files.directory, true)) }, enabled = !state.files.busy) { + HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) + } + if (state.files.hasMore) item { + TextButton(colors = neutral, onClick = { onIntent(RemoteWorkspaceIntent.BrowseFiles(state.files.directory, true)) }, enabled = !state.files.busy) { Text(stringResource(R.string.workspace_more_files)) } - if (state.files.directory.isNotEmpty()) { - OutlinedTextField(value = newDirectory, onValueChange = { newDirectory = it }, label = { Text(stringResource(R.string.workspace_new_directory)) }) - if (uploadFailed) Text(stringResource(R.string.workspace_upload_failed)) - TextButton(onClick = { uploadPath = newDirectory; uploadPicker.launch(arrayOf("*/*")) }, enabled = !state.files.busy && newDirectory.isNotBlank()) { Text(stringResource(R.string.workspace_upload_file)) } - TextButton(onClick = { onIntent(RemoteWorkspaceIntent.CreateFile(newDirectory)) }, enabled = !state.files.busy && newDirectory.isNotBlank()) { Text(stringResource(R.string.workspace_create_file)) } - if (state.files.file != null) { - TextButton(onClick = { onIntent(RemoteWorkspaceIntent.RenameFile(newDirectory)) }, enabled = !state.files.busy && newDirectory.isNotBlank()) { Text(stringResource(R.string.workspace_rename_file)) } - TextButton(onClick = { onIntent(RemoteWorkspaceIntent.DeleteFile) }, enabled = !state.files.busy) { Text(stringResource(R.string.workspace_delete_file)) } + } + } + RuntimeDownloadStatus(state.download, onIntent) + action?.let { current -> + val label = when (current) { + FileListAction.FILE -> R.string.workspace_create_file + FileListAction.DIRECTORY -> R.string.workspace_create_directory + FileListAction.RENAME -> R.string.session_rename + FileListAction.DELETE -> R.string.session_delete + FileListAction.UPLOAD -> R.string.workspace_upload_file + } + ModalBottomSheet(onDismissRequest = { if (!state.files.busy) action = null }, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true, confirmValueChange = { !state.files.busy }), + containerColor = MaterialTheme.colorScheme.surface) { + Column(Modifier.fillMaxWidth().padding(horizontal = 24.dp).padding(bottom = 24.dp).imePadding(), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { + TextButton(colors = neutral, enabled = !state.files.busy, onClick = { action = null }) { Text(stringResource(R.string.common_cancel)) } + Text(stringResource(label), style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f), textAlign = androidx.compose.ui.text.style.TextAlign.Center) + Button( + colors = ButtonDefaults.buttonColors(containerColor = if (current == FileListAction.DELETE) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurface, + contentColor = if (current == FileListAction.DELETE) MaterialTheme.colorScheme.onError else MaterialTheme.colorScheme.surface), + enabled = !state.files.busy && !submitted && (current == FileListAction.DELETE || name.isNotBlank()), onClick = { + if (current == FileListAction.UPLOAD) uploadPicker.launch(arrayOf("*/*")) + else { + submittedRevision = state.files.completedOperation; submitted = true + onIntent(when (current) { + FileListAction.FILE -> RemoteWorkspaceIntent.CreateFileEntry(name, false) + FileListAction.DIRECTORY -> RemoteWorkspaceIntent.CreateFileEntry(name, true) + FileListAction.RENAME -> RemoteWorkspaceIntent.RenameFileEntry(target, name) + else -> RemoteWorkspaceIntent.DeleteFileEntry(target) + }) + } + }) { Text(stringResource(label)) } } - TextButton(onClick = { onIntent(RemoteWorkspaceIntent.CreateDirectory(newDirectory)) }, enabled = !state.files.busy && newDirectory.isNotBlank()) { Text(stringResource(R.string.workspace_create_directory)) } + if (current == FileListAction.DELETE) Text(target, color = MaterialTheme.colorScheme.onSurfaceVariant) + else OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text(stringResource(R.string.workspace_file_name)) }, + modifier = Modifier.fillMaxWidth(), enabled = !state.files.busy, singleLine = true) + if (state.files.busy) LinearProgressIndicator(Modifier.fillMaxWidth()) + if (state.files.failed || uploadFailed) Text((if (uploadFailed) null else state.files.errorDetail) ?: stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error) } - if (state.files.failed) Text(stringResource(R.string.workspace_files_failed), color = MaterialTheme.colorScheme.error) } } + RuntimeFileEditorDialog(state.files, onIntent) } + +/** Projects transfer state; KMP retains the immutable host/path used by retry. */ +@Composable +internal fun RuntimeDownloadStatus(download: RemoteFileDownloadUiState, onIntent: (RemoteWorkspaceIntent) -> Unit) { + val target = when (download) { + RemoteFileDownloadUiState.None -> return + is RemoteFileDownloadUiState.Loading -> download.target + is RemoteFileDownloadUiState.AwaitingSave -> download.target + is RemoteFileDownloadUiState.Saved -> download.target + is RemoteFileDownloadUiState.Failed -> download.target + } + HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) + Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) { + Column(Modifier.weight(1f)) { + Text(target.displayName.ifBlank { target.path.substringAfterLast('/') }, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodySmall) + Text(download.statusText(), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + if (download is RemoteFileDownloadUiState.Loading || download is RemoteFileDownloadUiState.AwaitingSave) { + CircularProgressIndicator(Modifier.size(20.dp), color = MaterialTheme.colorScheme.onSurface, strokeWidth = 2.dp) + } + if (download is RemoteFileDownloadUiState.Failed && download.retryable) { + TextButton(onClick = { onIntent(RemoteWorkspaceIntent.RetryDownload) }, colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface)) { + Text(stringResource(R.string.chat_retry)) + } + } + } +} diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspacePickerDialog.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspacePickerDialog.kt new file mode 100644 index 0000000000..b68d377da9 --- /dev/null +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/remote/RuntimeWorkspacePickerDialog.kt @@ -0,0 +1,97 @@ +package com.openbitfun.mobile.app.ui.remote + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.openbitfun.mobile.app.R +import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceIntent +import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceUiState + +/** Native presentation only; directory access and workspace mutation stay in KMP. */ +@Composable +internal fun RuntimeWorkspacePickerDialog( + state: RemoteWorkspaceUiState.Ready, + onIntent: (RemoteWorkspaceIntent) -> Unit, + onDismiss: () -> Unit, +) { + val actionColors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface) + val selectionColors = RadioButtonDefaults.colors(selectedColor = MaterialTheme.colorScheme.onSurface) + var path by rememberSaveable { mutableStateOf("") } + var connectionId by rememberSaveable { mutableStateOf(null) } + var browsing by rememberSaveable { mutableStateOf(false) } + var submittedPath by rememberSaveable { mutableStateOf(null) } + var submittedConnectionId by rememberSaveable { mutableStateOf(null) } + LaunchedEffect(state.busy, state.selected, state.loadFailure, submittedPath, submittedConnectionId) { + val targetPath = submittedPath ?: return@LaunchedEffect + if (!state.busy) { + if (!state.loadFailure && state.selected?.path == targetPath && + state.selected?.remoteConnectionId == submittedConnectionId) onDismiss() + } + } + fun open(targetPath: String, targetConnection: String?, sshHost: String? = null) { + submittedPath = targetPath + submittedConnectionId = targetConnection + onIntent(RemoteWorkspaceIntent.SelectWorkspace(targetPath, targetConnection, sshHost, false)) + } + if (browsing) RuntimeDirectoryPickerDialog( + state.directoryPicker, connectionId, onIntent, + { path = it; browsing = false }, { browsing = false }, + ) + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(stringResource(R.string.workspace_open_path)) }, + text = { + Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(8.dp)) { + TextButton(colors = actionColors, onClick = { connectionId = null; path = "" }, enabled = !state.busy) { + RadioButton(colors = selectionColors, selected = connectionId == null, onClick = null) + Text(stringResource(R.string.workspace_target_device)) + } + state.savedConnections.forEach { connection -> + TextButton(colors = actionColors, onClick = { connectionId = connection.id; path = "" }, enabled = !state.busy) { + RadioButton(colors = selectionColors, selected = connectionId == connection.id, onClick = null) + Text(connection.name) + } + } + if (state.savedConnectionsFailure) Text(stringResource(R.string.workspace_connections_failed), + color = MaterialTheme.colorScheme.error) + OutlinedTextField(value = path, onValueChange = { path = it }, singleLine = true, + label = { Text(stringResource(R.string.workspace_target_path)) }, + enabled = !state.busy, modifier = Modifier.fillMaxWidth()) + TextButton(colors = actionColors, enabled = !state.busy, onClick = { + browsing = true + onIntent(RemoteWorkspaceIntent.BrowseWorkspaceDirectories(path.ifBlank { "/" }, connectionId, false)) + }) { Text(stringResource(R.string.workspace_choose_folder)) } + if (state.loadFailure) { + Text(stringResource(R.string.workspace_failed), color = MaterialTheme.colorScheme.error) + TextButton(colors = actionColors, onClick = { onIntent(RemoteWorkspaceIntent.Load) }, enabled = !state.busy) { + Text(stringResource(R.string.sessions_refresh)) + } + } + state.workspaces.forEach { workspace -> + TextButton(colors = actionColors, enabled = !state.busy, onClick = { + open(workspace.path, workspace.remoteConnectionId, workspace.remoteSshHost) + }, modifier = Modifier.fillMaxWidth()) { + Column(Modifier.fillMaxWidth()) { + Text(workspace.displayName, style = MaterialTheme.typography.bodyMedium) + Text(workspace.path, style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + } + } + }, + confirmButton = { + TextButton(colors = actionColors, enabled = !state.busy && path.isNotBlank(), onClick = { open(path.trim(), connectionId) }) { + Text(stringResource(R.string.workspace_open_path)) + } + }, + dismissButton = { TextButton(colors = actionColors, onClick = onDismiss) { Text(stringResource(R.string.common_cancel)) } }, + ) +} diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/MobileScreen.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/MobileScreen.kt index bda8de480d..082fd13347 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/MobileScreen.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/MobileScreen.kt @@ -1,5 +1,6 @@ package com.openbitfun.mobile.app.ui.shell +import com.openbitfun.mobile.app.ui.account.messageRes import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -187,6 +188,10 @@ internal fun MobileScreen() { } } + // Full-screen previews replace the conversation composition. Keep the + // platform document launcher alive across both presentation surfaces. + com.openbitfun.mobile.app.ui.remote.RemoteDownloadSaver(activeWorkspaceState, ::dispatchActiveWorkspace) + fun dispatchActiveSession(intent: RemoteSessionIntent) { when (controlSummary.source) { RemoteControlSource.ACCOUNT_DEVICE -> accountViewModel.dispatchSession(intent) @@ -276,11 +281,27 @@ internal fun MobileScreen() { // The button and the pane are the same sidebar; exactly one of them is real. val showMenu = sidebarWidth == 0 - var workspaceToolVisible by remember { mutableStateOf(null) } - LaunchedEffect(readyAccount?.selectedDeviceId) { workspaceToolVisible = null } + // Persist the owner with the visibility flag: rememberSaveable inputs alone + // do not validate the identity of restored values after recreation. + val workspacePickerOwner = listOf(readyAccount?.relayUrl.orEmpty(), readyAccount?.userId.orEmpty(), + readyAccount?.selectedDeviceId.orEmpty(), controlSummary.source.name) + var workspacePickerSavedOwner by rememberSaveable { mutableStateOf(workspacePickerOwner) } + var workspacePickerOpen by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(workspacePickerOwner) { + if (workspacePickerSavedOwner != workspacePickerOwner) { + workspacePickerOpen = false + workspacePickerSavedOwner = workspacePickerOwner + } + } + if (workspacePickerOpen && workspacePickerSavedOwner == workspacePickerOwner) { + (activeWorkspaceState as? RemoteWorkspaceUiState.Ready)?.let { ready -> + com.openbitfun.mobile.app.ui.remote.RuntimeWorkspacePickerDialog( + ready, ::dispatchActiveWorkspace, { workspacePickerOpen = false }, + ) + } + } (activeWorkspaceState as? RemoteWorkspaceUiState.Ready)?.let { ready -> - if (workspaceToolVisible == "files") com.openbitfun.mobile.app.ui.remote.RuntimeFilesDialog(ready, ::dispatchActiveWorkspace) { workspaceToolVisible = null } - if (workspaceToolVisible == "terminal") com.openbitfun.mobile.app.ui.remote.RuntimeTerminalDialog(ready.terminal, ::dispatchActiveWorkspace) { workspaceToolVisible = null } + if (ready.deviceTools.visible) com.openbitfun.mobile.app.ui.remote.DeviceToolsDialog(ready, ::dispatchActiveWorkspace) } val sidebar: @Composable () -> Unit = { AppSidebar( @@ -309,6 +330,9 @@ internal fun MobileScreen() { shell.openRemoteConnect() closeDrawer() }, + onRefreshRemoteDevices = { accountViewModel.dispatch(AccountIntent.RefreshDevices) }, + refreshingRemoteDevices = readyAccount?.refreshing == true, + directoryRefreshError = readyAccount?.refreshFailure?.let { stringResource(it.messageRes()) }, onRetryRemoteDevice = { dispatchActiveSession(RemoteSessionIntent.Load) dispatchActiveWorkspace(RemoteWorkspaceIntent.Load) @@ -323,7 +347,7 @@ internal fun MobileScreen() { closeDrawer() dispatchActiveSession(RemoteSessionIntent.Open(sessionId)) }, - onCreateRemoteInWorkspace = { path, connectionId, agentType -> + onCreateRemoteInWorkspace = { path, connectionId, sshHost, agentType -> dispatchActiveSession( RemoteSessionIntent.CreateSession( agentType = agentType, @@ -332,16 +356,18 @@ internal fun MobileScreen() { modelId = null, workspacePath = path, remoteConnectionId = connectionId, + remoteSshHost = sshHost, ), ) shell.show(MobileSurface.REMOTE) closeDrawer() }, onWorkspaceTool = { path, connectionId, terminal -> - dispatchActiveWorkspace(if (terminal) RemoteWorkspaceIntent.OpenDeviceTerminal(path, connectionId) else RemoteWorkspaceIntent.OpenDeviceFiles(path, connectionId)) - workspaceToolVisible = if (terminal) "terminal" else "files" + dispatchActiveWorkspace(RemoteWorkspaceIntent.OpenDeviceTools(path, connectionId)) + if (terminal) dispatchActiveWorkspace(RemoteWorkspaceIntent.SelectDeviceToolsPanel(com.openbitfun.mobile.core.feature.workspace.DeviceToolsPanel.TERMINAL)) closeDrawer() }, + onAddRemoteWorkspace = { workspacePickerOpen = true }, onOpenRemoteWorkspace = { path -> dispatchActiveWorkspace(RemoteWorkspaceIntent.SelectWorkspace(path)) shell.show(MobileSurface.REMOTE) @@ -426,7 +452,6 @@ internal fun MobileScreen() { onOpenSidebar = { compactDrawerOpen = true }, onBrowse = { shell.openRemoteConnect() }, onOpen = {}, - onOpenRemoteSettings = { shell.openRemoteConnect() }, ) } else WelcomeHome( signedIn = readyAccount != null, diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/AppSidebar.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/AppSidebar.kt index 791d19dcfb..1c2248ec6b 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/AppSidebar.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/AppSidebar.kt @@ -84,40 +84,20 @@ internal fun AppSidebar( onToggleSearch: () -> Unit, onScanDesktop: () -> Unit, onRetryRemoteDevice: () -> Unit, + onRefreshRemoteDevices: () -> Unit, + refreshingRemoteDevices: Boolean, + directoryRefreshError: String? = null, onSelectRemoteDevice: (String) -> Unit, onOpenRemoteSession: (String) -> Unit, - onCreateRemoteInWorkspace: (String, String?, String) -> Unit, + onCreateRemoteInWorkspace: (String, String?, String?, String) -> Unit, onOpenRemoteWorkspace: (String) -> Unit, + onAddRemoteWorkspace: (() -> Unit)? = null, onWorkspaceTool: (String, String?, Boolean) -> Unit, onDeleteRemoteSession: (String) -> Unit, onOpenSettings: () -> Unit, onOpenAccount: () -> Unit, modifier: Modifier, ) { - var toolsPicker by remember { mutableStateOf(false) } - var toolsPositionSelected by remember { mutableStateOf(false) } - var toolsConnection by remember { mutableStateOf(null) } - androidx.compose.runtime.LaunchedEffect(remoteSelectedDeviceId) { toolsPicker = false; toolsPositionSelected = false; toolsConnection = null } - if (toolsPicker) androidx.compose.material3.AlertDialog( - onDismissRequest = { toolsPicker = false; toolsPositionSelected = false; toolsConnection = null }, - title = { Text(stringResource(R.string.device_tools)) }, - text = { - Column(Modifier.verticalScroll(rememberScrollState())) { - if (!toolsPositionSelected) { - androidx.compose.material3.TextButton(onClick = { toolsConnection = null; toolsPositionSelected = true }) { Text(stringResource(R.string.workspace_target_device)) } - (workspaceState as? RemoteWorkspaceUiState.Ready)?.savedConnections.orEmpty().forEach { connection -> - androidx.compose.material3.TextButton(onClick = { toolsConnection = connection.id; toolsPositionSelected = true }) { Text(connection.name) } - } - } else { - androidx.compose.material3.TextButton(onClick = { toolsPicker = false; onWorkspaceTool("", toolsConnection, false) }) { Text(stringResource(R.string.workspace_browse_files)) } - androidx.compose.material3.TextButton(onClick = { toolsPicker = false; onWorkspaceTool("", toolsConnection, true) }) { Text(stringResource(R.string.workspace_terminal)) } - } - } - }, - confirmButton = {}, - dismissButton = { androidx.compose.material3.TextButton(onClick = { if (toolsPositionSelected) { toolsPositionSelected = false; toolsConnection = null } else toolsPicker = false }) { Text(stringResource(R.string.common_back)) } }, - ) - val signedIn = !accountUserId.isNullOrBlank() var remoteActionSession by remember { mutableStateOf(null) } var remoteActionAnchor by remember { mutableStateOf(IntRect.Zero) } @@ -150,6 +130,9 @@ internal fun AppSidebar( selectedSessionId = remoteSelectedSessionId.takeIf { remoteActive }, onConnect = onScanDesktop, onRetryActive = onRetryRemoteDevice, + onRefreshDevices = onRefreshRemoteDevices, + refreshingDevices = refreshingRemoteDevices, + directoryRefreshError = directoryRefreshError, onSelectDevice = onSelectRemoteDevice, onOpenSession = onOpenRemoteSession, onOpenActions = { row, anchor -> @@ -158,9 +141,11 @@ internal fun AppSidebar( }, onCreateInWorkspace = onCreateRemoteInWorkspace, onOpenWorkspace = onOpenRemoteWorkspace, + onAddWorkspace = onAddRemoteWorkspace, onWorkspaceTool = onWorkspaceTool, ) } + } // Over the list, not after it: the 84dp tail the list reserves is what @@ -172,7 +157,7 @@ internal fun AppSidebar( .padding(start = 20.dp, end = 20.dp, bottom = 16.dp), ) { if (signedIn) { - SidebarAuthenticatedFooter({ toolsPicker = true }, onOpenSettings) + SidebarAuthenticatedFooter({ onWorkspaceTool("", null, false) }, onOpenSettings) } else { SidebarSignedOutFooter( showScan = connectionPhase != ConnectionPhase.CONNECTED, diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarChrome.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarChrome.kt index 5c139fe88c..80096af7a0 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarChrome.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarChrome.kt @@ -62,12 +62,11 @@ internal fun SidebarCircleButton( @Composable internal fun ConnectionDot(phase: ConnectionPhase) { val tone = ConnectionStatusPresenter.tone(phase) - // Same three colours `ConversationView.ets#connectionColor()` picks from; - // BUSY is the one addition, because a dot that only ever goes green or grey - // cannot say "connecting" while it is still trying. + // Keep recovery neutral across native sidebars; the status label and + // accessibility state distinguish checking from a disconnected link. val color: Color = when (tone) { ConnectionTone.OK -> openBitFunColors.statusSuccess - ConnectionTone.BUSY -> MaterialTheme.colorScheme.tertiary + ConnectionTone.BUSY -> MaterialTheme.colorScheme.onSurfaceVariant ConnectionTone.ERROR -> MaterialTheme.colorScheme.error ConnectionTone.MUTED -> MaterialTheme.colorScheme.onSurfaceVariant } diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarRemoteWorkspaceSection.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarRemoteWorkspaceSection.kt index d61cc320da..dd8f781bf4 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarRemoteWorkspaceSection.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/shell/sidebar/SidebarRemoteWorkspaceSection.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.IntRect @@ -46,6 +47,7 @@ import androidx.compose.ui.unit.sp import com.openbitfun.mobile.app.ui.remote.ProjectCreateControl import com.openbitfun.mobile.core.feature.session.HarnessProfilePolicy import com.openbitfun.mobile.app.R +import com.openbitfun.mobile.app.ui.common.labelRes import com.openbitfun.mobile.core.feature.account.AccountDeviceUi import com.openbitfun.mobile.core.feature.connection.ConnectionPhase import com.openbitfun.mobile.core.feature.connection.ConnectionStatusPresenter @@ -75,15 +77,19 @@ internal fun SidebarRemoteWorkspaceSection( selectedSessionId: String?, onConnect: () -> Unit, onRetryActive: () -> Unit, + onRefreshDevices: () -> Unit, + refreshingDevices: Boolean, + directoryRefreshError: String? = null, onSelectDevice: (String) -> Unit, onOpenSession: (String) -> Unit, onOpenActions: (RemoteSidebarSessionRow, IntRect) -> Unit, - onCreateInWorkspace: (String, String?, String) -> Unit, + onCreateInWorkspace: (String, String?, String?, String) -> Unit, onOpenWorkspace: (String) -> Unit, + onAddWorkspace: (() -> Unit)? = null, onWorkspaceTool: (String, String?, Boolean) -> Unit, ) { val connected = ConnectionStatusPresenter.canReachSessions(connectionPhase) - val addConnectionLabel = stringResource(R.string.sidebar_add_connection) + val refreshDevicesLabel = stringResource(R.string.account_devices_refresh) val projectedDevices = devices val activeDeviceId = if (controlSource == RemoteControlSource.ACCOUNT_DEVICE) selectedDeviceId else null var expandedDeviceIds by rememberSaveable { mutableStateOf(emptyList()) } @@ -131,14 +137,16 @@ internal fun SidebarRemoteWorkspaceSection( modifier = Modifier .size(32.dp) .clip(CircleShape) - .clickable(role = Role.Button, onClick = onConnect) + .clickable(enabled = !refreshingDevices, role = Role.Button, onClick = onRefreshDevices) + .testTag("sidebar-refresh-devices") .semantics(mergeDescendants = true) { - contentDescription = addConnectionLabel + contentDescription = refreshDevicesLabel }, contentAlignment = Alignment.Center, ) { - Icon( - painterResource(R.drawable.ic_symbol_plus), + if (refreshingDevices) CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 1.5.dp) + else Icon( + painterResource(R.drawable.ic_symbol_arrow_clockwise), contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(17.dp), @@ -146,6 +154,12 @@ internal fun SidebarRemoteWorkspaceSection( } } + directoryRefreshError?.let { error -> + Text(error, fontSize = 12.sp, color = MaterialTheme.colorScheme.error, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp) + .clickable(enabled = !refreshingDevices, role = Role.Button, onClick = onRefreshDevices)) + } + if (projectedDevices.isEmpty()) { SidebarActiveDeviceBody( connected = connected, @@ -164,6 +178,7 @@ internal fun SidebarRemoteWorkspaceSection( canActOnSessions = true, onCreateInWorkspace = onCreateInWorkspace, onOpenWorkspace = onOpenWorkspace, + onAddWorkspace = onAddWorkspace, onWorkspaceTool = onWorkspaceTool, ) } else { @@ -195,8 +210,10 @@ internal fun SidebarRemoteWorkspaceSection( SidebarDeviceHeader( deviceName = device.name.ifBlank { device.id }, online = device.online, + selected = active, expanded = expanded, loading = loading, + phase = if (active) connectionPhase else null, onToggle = { if (active) { expandedDeviceIds = if (expanded) { @@ -218,6 +235,9 @@ internal fun SidebarRemoteWorkspaceSection( } }, ) + if (active && connectionPhase in listOf(ConnectionPhase.RECONNECTING, ConnectionPhase.FAILED, ConnectionPhase.DISCONNECTED)) { + SidebarConnectionRecovery(connectionPhase, onRetryActive) + } if (expanded) { SidebarActiveDeviceBody( connected = if (active) connected else shownWorkspace != null, @@ -234,6 +254,7 @@ internal fun SidebarRemoteWorkspaceSection( canActOnSessions = active, onCreateInWorkspace = onCreateInWorkspace, onOpenWorkspace = onOpenWorkspace, + onAddWorkspace = onAddWorkspace, onWorkspaceTool = onWorkspaceTool, ) } @@ -265,8 +286,9 @@ private fun SidebarActiveDeviceBody( onOpenSession: (String) -> Unit, onOpenActions: (RemoteSidebarSessionRow, IntRect) -> Unit, canActOnSessions: Boolean, - onCreateInWorkspace: (String, String?, String) -> Unit, + onCreateInWorkspace: (String, String?, String?, String) -> Unit, onOpenWorkspace: (String) -> Unit, + onAddWorkspace: (() -> Unit)? = null, onWorkspaceTool: (String, String?, Boolean) -> Unit, ) { val readyWorkspace = workspaceState @@ -281,6 +303,33 @@ private fun SidebarActiveDeviceBody( mutableStateOf(WORKSPACES_PER_BATCH) } + Row( + Modifier.fillMaxWidth().padding(top = 16.dp).height(48.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text(stringResource(R.string.sidebar_workspaces), fontSize = 14.sp, + fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.weight(1f)) + if (onAddWorkspace != null) { + androidx.compose.material3.IconButton(onClick = onAddWorkspace, + enabled = connected && canActOnSessions && readyWorkspace != null && !readyWorkspace.busy, + modifier = Modifier.testTag("sidebar-add-workspace")) { + Icon(painterResource(R.drawable.ic_symbol_plus), + contentDescription = stringResource(R.string.workspace_open_path), + modifier = Modifier.size(18.dp)) + } + } + } + + if (readyWorkspace?.catalog?.source == com.openbitfun.mobile.core.feature.workspace.WorkspaceCatalogSource.RECENT) { + Text( + stringResource(R.string.sidebar_legacy_workspace_catalog), + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), + ) + } + if (!connected) { ConnectDesktopRow(onConnect) } else if (failed) { @@ -297,8 +346,9 @@ private fun SidebarActiveDeviceBody( } else { entries.take(visibleWorkspaceCount).forEach { entry -> val path = entry.path + val identityKey = entry.key val workspaceSessions = entry.sessions - val collapsed = path in collapsedPaths + val collapsed = identityKey in collapsedPaths Column(modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp)) { Row( modifier = Modifier @@ -309,9 +359,9 @@ private fun SidebarActiveDeviceBody( role = Role.Button, onClick = { collapsedPaths = if (collapsed) { - collapsedPaths - path + collapsedPaths - identityKey } else { - collapsedPaths + path + collapsedPaths + identityKey } }, onLongClick = { onOpenWorkspace(path) }, @@ -355,7 +405,7 @@ private fun SidebarActiveDeviceBody( expanded = createMenuOpen, onToggle = { createMenuOpen = !createMenuOpen }, onDismiss = { createMenuOpen = false }, - onCreateAgent = { agent -> createMenuOpen = false; onCreateInWorkspace(path, entry.remoteConnectionId, agent) }, + onCreateAgent = { agent -> createMenuOpen = false; onCreateInWorkspace(path, entry.remoteConnectionId, entry.remoteSshHost, agent) }, ) Icon( painterResource( @@ -369,7 +419,7 @@ private fun SidebarActiveDeviceBody( } if (!collapsed) { - val limit = if (path in expandedSessionPaths) { + val limit = if (identityKey in expandedSessionPaths) { workspaceSessions.size } else { SESSIONS_PER_WORKSPACE @@ -388,7 +438,7 @@ private fun SidebarActiveDeviceBody( MoreRow( hidden = workspaceSessions.size - limit, startPadding = 26, - onClick = { expandedSessionPaths = expandedSessionPaths + path }, + onClick = { expandedSessionPaths = expandedSessionPaths + identityKey }, ) } } @@ -410,11 +460,14 @@ private fun SidebarActiveDeviceBody( private fun SidebarDeviceHeader( deviceName: String, online: Boolean, + selected: Boolean, expanded: Boolean, loading: Boolean, + phase: ConnectionPhase? = null, onToggle: () -> Unit, ) { val deviceLabel = deviceName + val healthLabel = phase?.let { stringResource(it.labelRes()) } Row( modifier = Modifier .fillMaxWidth() @@ -423,6 +476,7 @@ private fun SidebarDeviceHeader( .clickable(role = Role.Button, onClick = onToggle) .semantics(mergeDescendants = true) { contentDescription = deviceLabel + healthLabel?.let { stateDescription = it } } .padding(start = 10.dp, end = 6.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), @@ -437,15 +491,17 @@ private fun SidebarDeviceHeader( ) Text( deviceName, - fontSize = 15.sp, - fontWeight = FontWeight.Medium, + fontSize = 14.sp, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, color = if (online) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f), ) - if (!online) { + if (phase != null) { + ConnectionDot(phase) + } else if (!online) { Text( stringResource(R.string.sidebar_device_offline), fontSize = 12.sp, @@ -471,6 +527,22 @@ private fun SidebarDeviceHeader( } } +@Composable +internal fun SidebarConnectionRecovery(phase: ConnectionPhase, onRetry: () -> Unit) { + val retryLabel = stringResource(R.string.sidebar_device_retry) + Row( + modifier = Modifier.fillMaxWidth().height(44.dp).padding(start = 10.dp, end = 6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text(stringResource(phase.labelRes()), fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.weight(1f)) + IconButton(onClick = onRetry, modifier = Modifier.size(44.dp).testTag("sidebar-retry-connection")) { + Icon(painterResource(R.drawable.ic_symbol_arrow_clockwise), contentDescription = retryLabel, + tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(17.dp)) + } + } +} + @Composable private fun DeviceLoadingRow() { Row( @@ -608,8 +680,8 @@ private fun RemoteSessionRow( ) Text( sessionTitle, - fontSize = 15.sp, - fontWeight = if (selected) FontWeight.Medium else FontWeight.Normal, + fontSize = 13.sp, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, color = MaterialTheme.colorScheme.onSurface, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt index a35d5f5002..571d1e80b3 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt @@ -57,6 +57,7 @@ internal object MobileDesignColors { val WelcomeButton = Color(0xFFFFFFFF) val WelcomeButtonLabel = Color(0xFF171917) val WelcomeSecondary = Color(0xFFB9BCB9) + val StatusWarning = Color(0xFFFF8C00) } object Dark { @@ -108,6 +109,7 @@ internal object MobileDesignColors { val WelcomeButton = Color(0xFFFFFFFF) val WelcomeButtonLabel = Color(0xFF171917) val WelcomeSecondary = Color(0xFFB9BCB9) + val StatusWarning = Color(0xFFFF8C00) } } @@ -207,6 +209,11 @@ internal object MobileDesignGeometry { val RecentHomeTitleSize = 25.dp val RecentHomeMaxWidth = 560.dp val RecentHomeRowPadding = 18.dp + val ApprovalCardRadius = 12.dp + val ApprovalCardPadding = 12.dp + val ApprovalCardGap = 8.dp + val ApprovalActionRadius = 8.dp + val ApprovalActionHeight = 36.dp } internal object MobileDesignBreakpoints { diff --git a/src/apps/mobile/android/app/src/main/res/drawable-nodpi/approval_shield.png b/src/apps/mobile/android/app/src/main/res/drawable-nodpi/approval_shield.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb2022901ab1225e7d7ed273f97e49423c609 GIT binary patch literal 1324 zcmV+{1=IS8P)g&UE5TM>2u2i3TS2M_VzG7Q0~boG7}AGSORLo*-^|3= zbpDyioquj-Zsx!bfh6BK|C#?e_xmo;ph1Iv24BT_6bw-&JBKkd+jefj#pq{Hv5ty^U6>O!!2t^H zLcf8dxQ2p{x;ExaqdwpWR-iA8QM^gPsT>`jq+)H{CUcl%G2fu#YSamA;Q|Wor(!n+ z|KuDI`zW~bzZe}Xr(%E3|22-C6x?gvs1g`pA&0S)0dA$>NebSl;P@=2%imOV*up?! zY{AytCP!u&_c#?#gqk943wvh>aTXO1$0E94Y@^P6Z=J~GRNPL%4+YmP(>hMU2J1sP z=5%m*c)~5dz#gOG8IIxn+}te;Q1KiUzkA@{Wqm1QP0@&gnH9K7|@D{)BQ;dqyg!rQ#7?gp=pbRtyWuP%A1C2o$Xbj3gV^9VfgZkbeX)dd&XkK^p{R=a}pNS+j z$RUi)PcrDFKN4|#<}{3pP>+l@mia;s;pa=qB+F27SXzxk`%nHz6nh;cYfy1eaGPU_ zec_Kp9Z8pMJc4>;(#VET zitijFXq}JvA*fj7Ooi*JDxJ3?Kv7<5kgsS!O_wPh5nanAYa_M!LM z2;&~ZVk+KE<(}g;F+^CWmHq^+{Yt?@KI$XFx{&}6gfY`OP1Aj-0%#=%*(1rG=x278 z>Y8T+x7b#lgXe^4LY2U1i*?<7W_DnYEv&PQ{c+CN6ZZG0A}Cl!#h1A{5_RTt26!;3 zl+xFa zk>8|ln-%}wj5>nv;2bI*qv99u#zgDg;9Qhmc?Ye6**a6CLh$q>!^Wa7=_(0E#kr4D z@naVw#>{=HFO8WAws3ZMf3j~*NH5T}y84|Aa8Dib07^l4Ww0+Lh`v2xBHq0|4c~ze iV`(|84H`5+HSjM~GsYa4Z9J&}0000็ฆป็บฟๅฏ็”จ ๆ— ๆณ•ๅŠ ่ฝฝๅฐๅบ”็”จ๏ผŒ่ฏท้‡่ฏ• ๆ— ๆณ•ๆ‰“ๅผ€ๆŽˆๆƒ้กต้ข๏ผŒ่ฏท้‡่ฏ•ใ€‚ + ๆ—ง็‰ˆ่ฎพๅค‡๏ผšๆ˜พ็คบๆœ€่ฟ‘่ฎฟ้—ฎ็š„ๅทฅไฝœๅŒบ + ็ผ–่พ‘ๅ‚ๆ•ฐ + ๆ”ถ่ตทๅ‚ๆ•ฐ + ๆ–‡ไปถๅ + ๆ–‡ไปถๆ“ไฝœ + ็”ต่„‘ไธŠ็š„ๆ–‡ไปถๅทฒ่ขซไฟฎๆ”น๏ผŒๅฝ“ๅ‰่‰็จฟไปไฟ็•™ใ€‚่ฏทๅ…ˆๅคๅˆถ่‰็จฟ๏ผŒๅ†้‡ๆ–ฐๆ‰“ๅผ€ๆ–‡ไปถไปฅ่ฏปๅ–ๆœ€ๆ–ฐๅ†…ๅฎนใ€‚ + ๅŠ ่ฝฝๆ›ดๆ—ฉๆถˆๆฏๅคฑ่ดฅ๏ผŒ็‚นๆŒ‰้‡่ฏ•ใ€‚ + ๆญฃๅœจๅŠ ่ฝฝๆ›ดๆ—ฉๆถˆๆฏโ€ฆ diff --git a/src/apps/mobile/android/app/src/main/res/values/strings.xml b/src/apps/mobile/android/app/src/main/res/values/strings.xml index 1c577b39c4..885bc0b4fc 100644 --- a/src/apps/mobile/android/app/src/main/res/values/strings.xml +++ b/src/apps/mobile/android/app/src/main/res/values/strings.xml @@ -525,4 +525,12 @@ Available offline Unable to load apps. Please retry. Unable to open the authorization page. Please retry. + Older device: showing recent workspaces + Edit input + Hide input + File name + File actions + File changed on the computer. Your draft is preserved. Copy it before reopening the file to load the latest version. + Could not load earlier messages. Tap to retry. + Loading earlier messagesโ€ฆ diff --git a/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanelsTest.kt b/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanelsTest.kt index 5778cb113e..6228ad522c 100644 --- a/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanelsTest.kt +++ b/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/chat/tool/ToolInteractionPanelsTest.kt @@ -10,8 +10,8 @@ import org.junit.Test class ToolInteractionPanelsTest { @Test - fun approvalEditSupportIsTypedAsUnsupported() { - assertEquals(ToolApprovalEditSupport.UNSUPPORTED, ToolApprovalEditContract.support) + fun approvalEditSupportMatchesRuntimeUpdatedInputContract() { + assertEquals(ToolApprovalEditSupport.SUPPORTED, ToolApprovalEditContract.support) } private val questions = listOf( diff --git a/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicyTest.kt b/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicyTest.kt new file mode 100644 index 0000000000..6e1a2028f1 --- /dev/null +++ b/src/apps/mobile/android/app/src/test/kotlin/com/openbitfun/mobile/app/ui/remote/DocumentExportPolicyTest.kt @@ -0,0 +1,15 @@ +package com.openbitfun.mobile.app.ui.remote + +import org.junit.Assert.assertEquals +import org.junit.Test + +class DocumentExportPolicyTest { + @Test fun sourceAndExtensionlessFilesDoNotAcquirePlatformExtensions() { + // Android maps .ts to video; unknown source extensions and extensionless + // files have no MIME mapping. text/plain would append .txt in each case. + assertEquals("application/octet-stream", documentExportMimeType("text/plain", "video/mp2t")) + assertEquals("application/octet-stream", documentExportMimeType("text/plain", null)) + assertEquals("image/png", documentExportMimeType("image/png", "image/png")) + assertEquals("application/pdf", documentExportMimeType("application/pdf", "application/pdf")) + } +} diff --git a/src/apps/mobile/design-system/assets/approval-shield.svg b/src/apps/mobile/design-system/assets/approval-shield.svg new file mode 100644 index 0000000000..3194e2b785 --- /dev/null +++ b/src/apps/mobile/design-system/assets/approval-shield.svg @@ -0,0 +1,43 @@ + + diff --git a/src/apps/mobile/design-system/components/mobile-components.json b/src/apps/mobile/design-system/components/mobile-components.json index fb854431bf..9c813b9b21 100644 --- a/src/apps/mobile/design-system/components/mobile-components.json +++ b/src/apps/mobile/design-system/components/mobile-components.json @@ -8,6 +8,20 @@ "tokens": ["control_touch_size", "card", "line", "ink"], "platformNotes": "Keep the glyph's optical box separate from the touch target. Header and sidebar controls stay visually transparent at rest; reserve borders and elevation for the containing surface." }, + "interaction_question": { + "purpose": "Answer a runtime mailbox question independently of transcript tool state.", + "anatomy": ["question_heading", "choice_or_text_input", "submit_action", "failure_feedback"], + "states": ["pending", "editing", "submitting", "failed"], + "tokens": ["content_gutter", "body_medium", "body_small", "ink", "muted", "card", "primary_action", "content_on_action"], + "platformNotes": "Render a direct question form in the scrollable mailbox. Tool execution headers, result expansion and stop actions stay in the transcript or composer. Retain draft values through submission failure and queue reordering; remove the form only when authoritative mailbox state removes it." + }, + "runtime_file_browser": { + "purpose": "Browse and manage files on the selected controlled runtime without changing the active chat workspace.", + "anatomy": ["runtime_location", "path_caption", "single_row_navigation_sort_create_toolbar", "scrolling_file_rows", "row_overflow", "contextual_transfer_feedback", "on_demand_action_sheet"], + "states": ["loading", "ready", "empty", "failed", "action_draft", "action_busy", "action_failed", "editor"], + "tokens": ["control_touch_size", "content_gutter", "body_medium", "body_small", "ink", "muted", "line", "card", "primary_action", "content_on_action"], + "platformNotes": "Parent, refresh, sort and create share one toolbar row. Rows show file or folder glyphs and an overflow menu; mutation forms are not permanently visible. Compact forms use a bottom sheet with submit and cancel reachable while the native keyboard is open. Preserve failed drafts and route every action to the captured runtime provider. Native adapters own keyboard avoidance and file pickers." + }, "action_button": { "purpose": "Expresses one primary, secondary, quiet, or destructive workflow action with consistent emphasis.", "anatomy": ["touch_target", "action_label", "semantic_surface", "optional_hairline_border"], @@ -161,6 +175,36 @@ "states": ["cold_start", "reduced_motion", "backgrounded"], "tokens": ["page_bg", "ink", "brand_dot", "startup_brand"], "platformNotes": "Native cold-start overlay on HarmonyOS, Android and iOS; 6800ms timeline independent of network readiness. A cyan dot hops ahead of ten 42-unit letters with subtle letter bounce. At normalized text time 0.70โ€“0.86 it arcs back to the dotless i, settling at 7.35 units diameter with one fading halo. Text time is min(progress / 0.65 * 0.9, 0.9). Logo expands from 0.65 to 1 with a small overshoot during progress 0.66โ€“0.85 as the word moves down 42 units. Use a centered 280ร—240 stage, scaled down for narrow windows, 92-unit contour mark on Android/iOS and a 156vp mark on HarmonyOS, with platform-native soft sans typography. HarmonyOS positions the mark at (62, -34) to preserve separation from the settled wordmark. Fade the overlay over the last 3%. Reserve full glyph slots; no layout changes during reveal. Remove on completion or background and do not replay on activity recreation/foreground. Skip for reduced motion. Notification onboarding follows completion. The dedicated brand_dot token preserves identity independently of action/status colors." + }, + "permission_request_panel": { + "purpose": "Answers an independent runtime permission request below the conversation header, outside the transcript, matching mobile-web.", + "anatomy": [ + "warning_glyph_and_action", + "optional_source_identity", + "neutral_resource_preview", + "collapsed_input_editor", + "trailing_reject_and_allow_actions" + ], + "states": [ + "pending", + "editing", + "busy", + "failed" + ], + "tokens": [ + "approval_card_radius", + "approval_card_padding", + "approval_card_gap", + "approval_action_radius", + "approval_action_height", + "status_warning", + "card", + "soft", + "line", + "primary_action", + "content_on_action" + ], + "platformNotes": "Reference: desktop ChatInputApprovalBand and @openbitfun/ui raised Card, neutral resource Card, fill reject Button and primary allow Button. Preserve desktop 12-unit card radius/padding and 8-unit gaps, with 36-unit mobile actions. Resource content scrolls within the band; actions do not stretch across the card. JSON editing remains a quiet, collapsed mobile-web capability. status_warning ports desktop amber.550 for the short shield glyph: neither success/danger nor the blue file-link accent communicates pending approval." } } } diff --git a/src/apps/mobile/design-system/preview/generated/mobile-design-data.js b/src/apps/mobile/design-system/preview/generated/mobile-design-data.js index f4334ad465..332c89c447 100644 --- a/src/apps/mobile/design-system/preview/generated/mobile-design-data.js +++ b/src/apps/mobile/design-system/preview/generated/mobile-design-data.js @@ -197,6 +197,10 @@ export const mobileTokens = { "welcome_secondary": { "light": "#B9BCB9", "dark": "#B9BCB9" + }, + "status_warning": { + "light": "#FF8C00", + "dark": "#FF8C00" } }, "typography": { @@ -361,7 +365,12 @@ export const mobileTokens = { "recent_home_mark_size": 132, "recent_home_title_size": 25, "recent_home_max_width": 560, - "recent_home_row_padding": 18 + "recent_home_row_padding": 18, + "approval_card_radius": 12, + "approval_card_padding": 12, + "approval_card_gap": 8, + "approval_action_radius": 8, + "approval_action_height": 36 }, "breakpoints": { "wide": 600, @@ -398,6 +407,67 @@ export const mobileComponents = { ], "platformNotes": "Keep the glyph's optical box separate from the touch target. Header and sidebar controls stay visually transparent at rest; reserve borders and elevation for the containing surface." }, + "interaction_question": { + "purpose": "Answer a runtime mailbox question independently of transcript tool state.", + "anatomy": [ + "question_heading", + "choice_or_text_input", + "submit_action", + "failure_feedback" + ], + "states": [ + "pending", + "editing", + "submitting", + "failed" + ], + "tokens": [ + "content_gutter", + "body_medium", + "body_small", + "ink", + "muted", + "card", + "primary_action", + "content_on_action" + ], + "platformNotes": "Render a direct question form in the scrollable mailbox. Tool execution headers, result expansion and stop actions stay in the transcript or composer. Retain draft values through submission failure and queue reordering; remove the form only when authoritative mailbox state removes it." + }, + "runtime_file_browser": { + "purpose": "Browse and manage files on the selected controlled runtime without changing the active chat workspace.", + "anatomy": [ + "runtime_location", + "path_caption", + "single_row_navigation_sort_create_toolbar", + "scrolling_file_rows", + "row_overflow", + "contextual_transfer_feedback", + "on_demand_action_sheet" + ], + "states": [ + "loading", + "ready", + "empty", + "failed", + "action_draft", + "action_busy", + "action_failed", + "editor" + ], + "tokens": [ + "control_touch_size", + "content_gutter", + "body_medium", + "body_small", + "ink", + "muted", + "line", + "card", + "primary_action", + "content_on_action" + ], + "platformNotes": "Parent, refresh, sort and create share one toolbar row. Rows show file or folder glyphs and an overflow menu; mutation forms are not permanently visible. Compact forms use a bottom sheet with submit and cancel reachable while the native keyboard is open. Preserve failed drafts and route every action to the captured runtime provider. Native adapters own keyboard avoidance and file pickers." + }, "action_button": { "purpose": "Expresses one primary, secondary, quiet, or destructive workflow action with consistent emphasis.", "anatomy": [ @@ -1003,6 +1073,36 @@ export const mobileComponents = { "startup_brand" ], "platformNotes": "Native cold-start overlay on HarmonyOS, Android and iOS; 6800ms timeline independent of network readiness. A cyan dot hops ahead of ten 42-unit letters with subtle letter bounce. At normalized text time 0.70โ€“0.86 it arcs back to the dotless i, settling at 7.35 units diameter with one fading halo. Text time is min(progress / 0.65 * 0.9, 0.9). Logo expands from 0.65 to 1 with a small overshoot during progress 0.66โ€“0.85 as the word moves down 42 units. Use a centered 280ร—240 stage, scaled down for narrow windows, 92-unit contour mark on Android/iOS and a 156vp mark on HarmonyOS, with platform-native soft sans typography. HarmonyOS positions the mark at (62, -34) to preserve separation from the settled wordmark. Fade the overlay over the last 3%. Reserve full glyph slots; no layout changes during reveal. Remove on completion or background and do not replay on activity recreation/foreground. Skip for reduced motion. Notification onboarding follows completion. The dedicated brand_dot token preserves identity independently of action/status colors." + }, + "permission_request_panel": { + "purpose": "Answers an independent runtime permission request below the conversation header, outside the transcript, matching mobile-web.", + "anatomy": [ + "warning_glyph_and_action", + "optional_source_identity", + "neutral_resource_preview", + "collapsed_input_editor", + "trailing_reject_and_allow_actions" + ], + "states": [ + "pending", + "editing", + "busy", + "failed" + ], + "tokens": [ + "approval_card_radius", + "approval_card_padding", + "approval_card_gap", + "approval_action_radius", + "approval_action_height", + "status_warning", + "card", + "soft", + "line", + "primary_action", + "content_on_action" + ], + "platformNotes": "Reference: desktop ChatInputApprovalBand and @openbitfun/ui raised Card, neutral resource Card, fill reject Button and primary allow Button. Preserve desktop 12-unit card radius/padding and 8-unit gaps, with 36-unit mobile actions. Resource content scrolls within the band; actions do not stretch across the card. JSON editing remains a quiet, collapsed mobile-web capability. status_warning ports desktop amber.550 for the short shield glyph: neither success/danger nor the blue file-link accent communicates pending approval." } } }; diff --git a/src/apps/mobile/design-system/tokens/mobile-tokens.json b/src/apps/mobile/design-system/tokens/mobile-tokens.json index a298153346..9bdf8d1d2c 100644 --- a/src/apps/mobile/design-system/tokens/mobile-tokens.json +++ b/src/apps/mobile/design-system/tokens/mobile-tokens.json @@ -196,6 +196,10 @@ "welcome_secondary": { "light": "#B9BCB9", "dark": "#B9BCB9" + }, + "status_warning": { + "light": "#FF8C00", + "dark": "#FF8C00" } }, "typography": { @@ -360,7 +364,12 @@ "recent_home_mark_size": 132, "recent_home_title_size": 25, "recent_home_max_width": 560, - "recent_home_row_padding": 18 + "recent_home_row_padding": 18, + "approval_card_radius": 12, + "approval_card_padding": 12, + "approval_card_gap": 8, + "approval_action_radius": 8, + "approval_action_height": 36 }, "breakpoints": { "wide": 600, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/entryability/EntryAbility.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/entryability/EntryAbility.ets index a0ee390a78..13f5438b36 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/entryability/EntryAbility.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/entryability/EntryAbility.ets @@ -16,14 +16,15 @@ export default class EntryAbility extends UIAbility { if (scenarioId === 'welcome-home' || scenarioId === 'connected-conversation' || scenarioId === 'streaming-dark' || scenarioId === 'reconnecting-wide' || scenarioId === 'narrow-multiline' || - scenarioId === 'fold-context' || scenarioId === 'long-reading') { + scenarioId === 'fold-context' || scenarioId === 'long-reading' || + scenarioId === 'interaction-mailbox' || scenarioId === 'interaction-mailbox-dark') { this.initialPage = 'pages/preview/MobileDesignGallery'; this.previewStorage = new LocalStorage(); this.previewStorage.setOrCreate('scenarioId', scenarioId); AppStorage.setOrCreate('scenarioId', scenarioId); } try { - const colorMode = scenarioId === 'streaming-dark' + const colorMode = scenarioId === 'streaming-dark' || scenarioId === 'interaction-mailbox-dark' ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK : scenarioId === 'connected-conversation' || scenarioId === 'reconnecting-wide' ? ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets index 9f53b6ca8c..d006f83c3e 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets @@ -59,6 +59,7 @@ export class MobileDesignColors { static readonly welcomeButton: MobileColorPair = new MobileColorPair("#FFFFFF", "#FFFFFF"); static readonly welcomeButtonLabel: MobileColorPair = new MobileColorPair("#171917", "#171917"); static readonly welcomeSecondary: MobileColorPair = new MobileColorPair("#B9BCB9", "#B9BCB9"); + static readonly statusWarning: MobileColorPair = new MobileColorPair("#FF8C00", "#FF8C00"); } export class MobileTypographyToken { @@ -169,6 +170,11 @@ export class MobileDesignGeometry { static readonly recentHomeTitleSize: number = 25; static readonly recentHomeMaxWidth: number = 560; static readonly recentHomeRowPadding: number = 18; + static readonly approvalCardRadius: number = 12; + static readonly approvalCardPadding: number = 12; + static readonly approvalCardGap: number = 8; + static readonly approvalActionRadius: number = 8; + static readonly approvalActionHeight: number = 36; } export class MobileDesignBreakpoints { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets index d169674f5d..ee07feed63 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets @@ -5,6 +5,7 @@ export const EN_US_MESSAGES: [string, string][] = [ ['workspaceTools.edit', 'Edit'], ['workspaceTools.lineNumbers', 'Line numbers'], ['workspaceTools.unsaved', 'Unsaved changes'], + ['workspaceTools.fileConflict', 'File changed on the computer. Your draft is preserved. Copy it before reopening the file to load the latest version.'], ['workspaceTools.discard', 'Discard changes'], ['workspaceTools.nameAsc', 'Name Aโ€“Z'], ['workspaceTools.nameDesc', 'Name Zโ€“A'], @@ -18,10 +19,12 @@ export const EN_US_MESSAGES: [string, string][] = [ ['workspaceTools.download', 'Download'], ['workspaceTools.title', 'Device tools'], ['workspaceTools.files', 'Files'], + ['workspaceTools.selectTerminal', 'Select an existing terminal'], ['workspaceTools.terminal', 'Terminal'], ['workspaceTools.up', 'Parent'], ['workspaceTools.refresh', 'Refresh'], ['workspaceTools.more', 'Load more'], + ['workspaceTools.fileName', 'File name'], ['workspaceTools.path', 'Absolute path on controlled runtime'], ['workspaceTools.newFile', 'New file'], ['workspaceTools.mkdir', 'New folder'], @@ -190,6 +193,7 @@ export const EN_US_MESSAGES: [string, string][] = [ ['sidebar.generalChat', 'General chat'], ['sidebar.conversations', 'Recent chats'], ['sidebar.devices', 'Devices'], + ['sidebar.legacyWorkspaceCatalog', 'Older device: showing recent workspaces'], ['sidebar.workspaces', 'Workspaces'], ['sidebar.deviceOnline', 'Online'], ['sidebar.addWorkspace', 'Add workspace'], @@ -505,6 +509,7 @@ export const EN_US_MESSAGES: [string, string][] = [ ['chat.removeImage', 'Remove image'], ['chat.selectModel', 'Select model'], ['chat.loadOlder', 'Load older messages'], + ['chat.loadOlderFailed', 'Could not load earlier messages. Tap to retry.'], ['chat.inputPlaceholder', 'Ask OpenBitFun'], ['chat.voiceListeningPlaceholder', 'Listening, please speak...'], ['chat.queuedAfterRunningTurn', 'Queued. It will run after the current task finishes.'], @@ -539,6 +544,11 @@ export const EN_US_MESSAGES: [string, string][] = [ ['chat.responseFailed', 'This response failed'], ['chat.error', 'Error'], ['chat.output', 'Output'], + ['chat.interactionLoadFailed', 'Unable to load pending interactions'], + ['chat.approvalFailed', 'Unable to submit response. Please retry.'], + ['chat.approvalRequired', 'Approval required'], + ['chat.editApprovalInput', 'Edit input'], + ['chat.hideApprovalInput', 'Hide input'], ['chat.approve', 'Approve'], ['chat.reject', 'Reject'], ['chat.cancelTool', 'Cancel tool'], diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets index 978bf692a7..da286b2c76 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets @@ -5,6 +5,7 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['workspaceTools.edit', '็ผ–่พ‘'], ['workspaceTools.lineNumbers', '่กŒๅท'], ['workspaceTools.unsaved', 'ๆœ‰ๆœชไฟๅญ˜็š„ๆ›ดๆ”น'], + ['workspaceTools.fileConflict', '็”ต่„‘ไธŠ็š„ๆ–‡ไปถๅทฒ่ขซไฟฎๆ”น๏ผŒๅฝ“ๅ‰่‰็จฟไปไฟ็•™ใ€‚่ฏทๅ…ˆๅคๅˆถ่‰็จฟ๏ผŒๅ†้‡ๆ–ฐๆ‰“ๅผ€ๆ–‡ไปถไปฅ่ฏปๅ–ๆœ€ๆ–ฐๅ†…ๅฎนใ€‚'], ['workspaceTools.discard', 'ๆ”พๅผƒๆ›ดๆ”น'], ['workspaceTools.nameAsc', 'ๅ็งฐๅ‡ๅบ'], ['workspaceTools.nameDesc', 'ๅ็งฐ้™ๅบ'], @@ -18,10 +19,12 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['workspaceTools.download', 'ไธ‹่ฝฝ'], ['workspaceTools.title', '่ฎพๅค‡ๅทฅๅ…ท'], ['workspaceTools.files', 'ๆ–‡ไปถ'], + ['workspaceTools.selectTerminal', '้€‰ๆ‹ฉๅทฒๆœ‰็ปˆ็ซฏ'], ['workspaceTools.terminal', '็ปˆ็ซฏ'], ['workspaceTools.up', 'ไธŠ็บง็›ฎๅฝ•'], ['workspaceTools.refresh', 'ๅˆทๆ–ฐ'], ['workspaceTools.more', 'ๅŠ ่ฝฝๆ›ดๅคš'], + ['workspaceTools.fileName', 'ๆ–‡ไปถๅ'], ['workspaceTools.path', '่ขซๆŽง่ฟ่กŒ็ซฏ็ปๅฏน่ทฏๅพ„'], ['workspaceTools.newFile', 'ๆ–ฐๅปบๆ–‡ไปถ'], ['workspaceTools.mkdir', 'ๆ–ฐๅปบๆ–‡ไปถๅคน'], @@ -190,6 +193,7 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['sidebar.generalChat', 'ๆ™ฎ้€šๅฏน่ฏ'], ['sidebar.conversations', 'ๆœ€่ฟ‘ๅฏน่ฏ'], ['sidebar.devices', '่ฎพๅค‡'], + ['sidebar.legacyWorkspaceCatalog', 'ๆ—ง็‰ˆ่ฎพๅค‡๏ผšๆ˜พ็คบๆœ€่ฟ‘่ฎฟ้—ฎ็š„ๅทฅไฝœๅŒบ'], ['sidebar.workspaces', 'ๅทฅไฝœๅŒบ'], ['sidebar.deviceOnline', 'ๅœจ็บฟ'], ['sidebar.addWorkspace', 'ๆทปๅŠ ๅทฅไฝœๅŒบ'], @@ -505,6 +509,7 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['chat.removeImage', '็งป้™คๅ›พ็‰‡'], ['chat.selectModel', '้€‰ๆ‹ฉๆจกๅž‹'], ['chat.loadOlder', 'ๅŠ ่ฝฝๆ›ดๆ—ฉๆถˆๆฏ'], + ['chat.loadOlderFailed', 'ๅŠ ่ฝฝๆ›ดๆ—ฉๆถˆๆฏๅคฑ่ดฅ๏ผŒ็‚นๆŒ‰้‡่ฏ•ใ€‚'], ['chat.inputPlaceholder', 'ๅ‘ OpenBitFun ๆ้—ฎ'], ['chat.voiceListeningPlaceholder', 'ๆญฃๅœจๅฌ๏ผŒ่ฏท่ฏด่ฏ...'], ['chat.queuedAfterRunningTurn', 'ๅทฒๆŽ’้˜Ÿ๏ผŒๅฝ“ๅ‰ไปปๅŠก็ป“ๆŸๅŽๆ‰ง่กŒ'], @@ -539,6 +544,11 @@ export const ZH_CN_MESSAGES: [string, string][] = [ ['chat.responseFailed', 'ๆœฌๆฌกๅ›žๅคๅคฑ่ดฅ'], ['chat.error', '้”™่ฏฏ'], ['chat.output', '่พ“ๅ‡บ'], + ['chat.interactionLoadFailed', 'ๅพ…ๅค„็†ไบคไบ’ๅŠ ่ฝฝๅคฑ่ดฅ'], + ['chat.approvalFailed', 'ๆไบคๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•'], + ['chat.approvalRequired', '้œ€่ฆๆ‰นๅ‡†'], + ['chat.editApprovalInput', '็ผ–่พ‘ๅ‚ๆ•ฐ'], + ['chat.hideApprovalInput', 'ๆ”ถ่ตทๅ‚ๆ•ฐ'], ['chat.approve', 'ๆ‰นๅ‡†'], ['chat.reject', 'ๆ‹’็ป'], ['chat.cancelTool', 'ๅ–ๆถˆๅทฅๅ…ท'], diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/model/InteractionMailbox.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/model/InteractionMailbox.ets new file mode 100644 index 0000000000..c3dc5116b9 --- /dev/null +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/model/InteractionMailbox.ets @@ -0,0 +1,51 @@ +import { RemoteQuestionAnswerPayload } from './RemoteModels'; + +export interface PermissionSource { identity: string; } +export interface PendingPermission { + requestId: string; + sessionId: string; + toolCallId?: string; + action: string; + resources: string[]; + source?: PermissionSource; +} +export interface PendingQuestion { + toolId: string; + sessionId: string; + questions: Object; +} +export interface PermissionMailbox { requests: PendingPermission[]; revision: number; } +export interface QuestionMailbox { questions: PendingQuestion[]; revision: number; } +export interface SessionInteractionMailbox { + sessionId: string; + permissions: PermissionMailbox; + userQuestions: QuestionMailbox; +} + +/** Separate from persisted transcript and from the active turn's lifetime. */ +export class InteractionMailboxState { + sessionId: string = ''; + requests: PendingPermission[] = []; + questions: PendingQuestion[] = []; + ownedToolIds: string[] = []; + busy: boolean = false; + failure: string = ''; + + ownsToolInteraction(toolId: string): boolean { + return toolId.length > 0 && (this.ownedToolIds.includes(toolId) || + this.questions.some((item: PendingQuestion): boolean => item.toolId === toolId) || + this.requests.some((item: PendingPermission): boolean => item.toolCallId === toolId)); + } +} + +export enum InteractionMailboxActionType { + Refresh = 'refresh', Approve = 'approve', Reject = 'reject', + StartQuestion = 'start_question', Answer = 'answer' +} +export interface InteractionMailboxAction { + sessionId: string; + type: InteractionMailboxActionType; + id?: string; + updatedInput?: Object; + answers?: RemoteQuestionAnswerPayload; +} diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/model/RemoteModels.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/model/RemoteModels.ets index d98c745d46..a21ec0f92e 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/model/RemoteModels.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/model/RemoteModels.ets @@ -6,7 +6,7 @@ export class EncryptedPayload { nonce: string = ''; } -export interface WorkspaceInfo { +export interface WorkspaceInfo extends RemoteWorkspaceIdentity { remoteConnectionId?: string; remoteSshHost?: string; path: string; @@ -25,7 +25,7 @@ export interface InitialSyncResult { capabilities?: string[]; } -export interface RecentWorkspaceEntry { +export interface RecentWorkspaceEntry extends RemoteWorkspaceIdentity { remoteConnectionId?: string; remoteSshHost?: string; path: string; @@ -34,6 +34,12 @@ export interface RecentWorkspaceEntry { workspaceKind: string; } +export interface RemoteWorkspaceIdentity { + path: string; + remoteConnectionId?: string; + remoteSshHost?: string; +} + export interface RemoteSession { id: string; title: string; @@ -44,6 +50,8 @@ export interface RemoteSession { messageCount: number; workspacePath?: string; workspaceName?: string; + /** Optional for legacy caches; assigned from the actual scoped listing request. */ + workspaceIdentity?: RemoteWorkspaceIdentity; pinned?: boolean; titleGenerated?: boolean; /** Stamped by the phone after mapping; the wire payload has no device id. */ @@ -67,6 +75,7 @@ export interface ChatMessage { } export interface SessionSummary { + remoteSshHost?: string; remoteConnectionId?: string; sessionId: string; title: string; @@ -76,6 +85,7 @@ export interface SessionSummary { } export interface CreateSessionOptions { + remoteSshHost?: string; remoteConnectionId?: string; agentType: string; title: string; @@ -105,6 +115,7 @@ export interface SavedRuntimeConnection { id: string; name: string; host: string export interface SavedRuntimeConnectionsResponse extends CommandStatusResponse { ok: boolean; value?: SavedRuntimeConnection[]; error?: string; } export interface RemoteCommand { + remote_ssh_host?: string; command?: string; args?: Object; remote_connection_id?: string; @@ -184,6 +195,7 @@ export interface RecentWorkspaceEntryResponse { } export interface RecentWorkspaceListResponse extends CommandStatusResponse { + opened_workspaces?: RecentWorkspaceEntryResponse[] | null; workspaces?: RecentWorkspaceEntryResponse[]; } @@ -288,6 +300,7 @@ export interface SessionListResult { } export interface CreateSessionResponse extends CommandStatusResponse { + remote_ssh_host?: string; workspace_path?: string; remote_connection_id?: string; session_id?: string; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/AppRootPresentationActions.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/AppRootPresentationActions.ets index 3e8d8e2e26..794879af7e 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/AppRootPresentationActions.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/AppRootPresentationActions.ets @@ -36,7 +36,7 @@ export interface RemoteHomePresentationActions { readonly refresh: () => void; readonly showWorkspaces: () => void; readonly showAssistants: () => void; - readonly selectWorkspace: (path: string, deviceId?: string, connectionId?: string) => void; + readonly selectWorkspace: (path: string, deviceId?: string, connectionId?: string, sshHost?: string) => void; readonly selectAssistant: (path: string) => void; readonly cancelWorkspace: () => void; readonly cancelAssistant: () => void; @@ -48,12 +48,12 @@ export interface RemoteHomePresentationActions { readonly clearPairing: () => void; readonly create: (agentType: string) => void; readonly createInPlace: (agentType: string) => void; - readonly createInWorkspace: (path: string, agentType: string, deviceId?: string, connectionId?: string) => void; - readonly createInWorkspaceInPlace: (path: string, agentType: string, deviceId?: string, connectionId?: string) => void; + readonly createInWorkspace: (path: string, agentType: string, deviceId?: string, connectionId?: string, sshHost?: string) => void; + readonly createInWorkspaceInPlace: (path: string, agentType: string, deviceId?: string, connectionId?: string, sshHost?: string) => void; readonly selectDirectoryDevice: (deviceId: string) => void; readonly addDirectoryWorkspace: (deviceId: string) => void; readonly retryDirectoryDevice: (deviceId: string) => void; - readonly loadWorkspaceSessions: (deviceId: string, path: string) => Promise; + readonly loadWorkspaceSessions: (deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string) => Promise; readonly openSession: (session: RemoteSession) => void; readonly openSessionInPlace: (session: RemoteSession) => void; readonly deleteSession: (session: RemoteSession) => void; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntent.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntent.ets index c58e54c028..e6a5da90b1 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntent.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntent.ets @@ -1,7 +1,9 @@ +import { InteractionMailboxAction } from '../../model/InteractionMailbox'; import { ConversationUiQuestionAnswer } from '../state/ConversationUiModels'; import { FilePreviewRequest } from '../../model/FilePreviewTarget'; export enum ConversationIntentType { + Mailbox = 'mailbox', OpenSidebar = 'open_sidebar', Back = 'back', NewSession = 'new_session', @@ -47,6 +49,7 @@ export class ConversationIntent { readonly answers?: ConversationUiQuestionAnswer; readonly filePreviewRequest?: FilePreviewRequest; readonly planBuildRequest?: PlanBuildRequest; + mailboxAction?: InteractionMailboxAction; constructor( type: ConversationIntentType, @@ -68,6 +71,11 @@ export class ConversationIntent { } export class ConversationIntents { + static mailbox(action: InteractionMailboxAction): ConversationIntent { + const intent = new ConversationIntent(ConversationIntentType.Mailbox); + intent.mailboxAction = action; + return intent; + } static simple(type: ConversationIntentType): ConversationIntent { return new ConversationIntent(type); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntentDispatcher.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntentDispatcher.ets index 40c8a8f11d..2671a3576b 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntentDispatcher.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/ConversationIntentDispatcher.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxAction } from '../../model/InteractionMailbox'; import { RemoteQuestionAnswerPayload, RemoteSession } from '../../model/RemoteModels'; import { AppRoute, AppRouteContract } from '../navigation/AppRouteContract'; import { ConversationIntent, ConversationIntentType } from './ConversationIntent'; @@ -5,6 +6,7 @@ import { toRemoteQuestionAnswer } from '../state/ConversationUiModels'; import { FilePreviewRequest } from '../../model/FilePreviewTarget'; export interface ConversationIntentDispatcherHooks { + readonly mailbox: (action: InteractionMailboxAction) => Promise; readonly openSidebar: () => void; readonly back: () => void; readonly newRemoteSession: () => void; @@ -38,6 +40,7 @@ export class ConversationIntentDispatcher { dispatch(route: AppRoute, intent: ConversationIntent): void { switch (intent.type) { + case ConversationIntentType.Mailbox: if (intent.mailboxAction) void this.hooks.mailbox(intent.mailboxAction); return; case ConversationIntentType.OpenSidebar: this.hooks.openSidebar(); return; case ConversationIntentType.Back: this.hooks.back(); return; case ConversationIntentType.NewSession: this.hooks.newRemoteSession(); return; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppRootOverlaySurfaces.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppRootOverlaySurfaces.ets index 5619628c1b..a8bd1961b8 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppRootOverlaySurfaces.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppRootOverlaySurfaces.ets @@ -75,6 +75,8 @@ export struct AppSidebarSurface { savedConnectionsTargetId: this.remotePageState.savedConnectionsTargetId, workspaceName: this.remotePageState.workspaceName, workspacePath: this.remotePageState.workspacePath, + workspaceConnectionId: this.remotePageState.workspaceConnectionId, + workspaceSshHost: this.remotePageState.workspaceSshHost, workspaceKind: this.remotePageState.workspaceKind, connectionState: this.remotePageState.connectionState, controlTargetType: this.remotePageState.controlTargetType, @@ -87,18 +89,19 @@ export struct AppSidebarSurface { REMOTE_CAPABILITY_HARNESS_PROFILES_V1), workspacePickerPlacement: this.sessionDetailsPlacement, onOpenSession: this.actions.onSidebar.openSession, - onCreateInWorkspace: (deviceId: string, path: string, agentType: string, connectionId?: string) => { + onCreateInWorkspace: (deviceId: string, path: string, agentType: string, connectionId?: string, sshHost?: string) => { this.actions.onSidebar.close(); - this.actions.onRemoteHome.createInWorkspace(path, agentType, deviceId, connectionId); + this.actions.onRemoteHome.createInWorkspace(path, agentType, deviceId, connectionId, sshHost); }, - onOpenWorkspace: (deviceId: string, path: string, connectionId?: string) => { - this.actions.onRemoteHome.selectWorkspace(path, deviceId, connectionId); + onOpenWorkspace: (deviceId: string, path: string, connectionId?: string, sshHost?: string) => { + this.actions.onRemoteHome.selectWorkspace(path, deviceId, connectionId, sshHost); }, - onExpandWorkspace: async (deviceId: string, path: string): Promise => { - await this.actions.onRemoteHome.loadWorkspaceSessions(deviceId, path); + onExpandWorkspace: async (deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise => { + await this.actions.onRemoteHome.loadWorkspaceSessions(deviceId, path, remoteConnectionId, remoteSshHost); }, onSelectDevice: this.actions.onRemoteHome.selectDirectoryDevice, onRetryDevice: this.actions.onRemoteHome.retryDirectoryDevice, + onReconnect: this.actions.onRemoteHome.reconnect, onAddWorkspace: this.actions.onRemoteHome.addDirectoryWorkspace, onRefreshDevices: this.actions.onSidebar.refreshDevices, onAddDevice: this.actions.onSidebar.addDesktop diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppSidebar.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppSidebar.ets index 4bf078609c..12e24926fb 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppSidebar.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppSidebar.ets @@ -16,8 +16,6 @@ export struct AppSidebar { @Param workspacePath: string = ''; @Param controlTargetDeviceId: string = ''; @Param supportsHarnessProfiles: boolean = false; - @Local showToolsPicker: boolean = false; - @Local toolsPickerTargetId: string = ''; @Param toolLocations: SavedRuntimeConnection[] = []; @Event onWorkspaceTools: (intent: WorkspaceToolsIntent) => void = (_intent: WorkspaceToolsIntent) => {}; @Param accountUserId: string = ''; @@ -262,18 +260,6 @@ export struct AppSidebar { .opacity(this.connectionState === 'connected' ? 1 : 0.45) .accessibilityText(RemoteI18n.t('workspaceTools.title')) .stateEffect(true) - .bindPopup(this.showToolsPicker, { - builder: () => { this.DeviceToolsPicker() }, - placement: Placement.Top, - popupColor: TRANSPARENT, - enableArrow: false, - autoCancel: true, - mask: false, - targetSpace: 6, - onStateChange: (event) => { - if (!event.isVisible) this.showToolsPicker = false; - } - }) .onClick(() => { this.requestDeviceTools(); }) Blank() @@ -343,39 +329,9 @@ export struct AppSidebar { .zIndex(2) } - @Monitor('controlTargetDeviceId', 'workspacePath', 'workspaceKind', 'connectionState', 'isBusy', 'supportsHarnessProfiles') - private closeStaleToolsPicker(): void { - this.showToolsPicker = false; - } - private requestDeviceTools(): void { if (this.connectionState !== 'connected') return; - this.toolsPickerTargetId = this.controlTargetDeviceId; - this.showToolsPicker = !this.showToolsPicker; - } - - @Builder - private DeviceToolsPicker() { - Scroll() { - Column({ space: 4 }) { - Text(RemoteI18n.t('workspaceTools.selectLocation')) - .fontSize(MobileDesignTypography.labelSmall.size).fontColor(SUBTLE) - .padding(10).width('100%') - Button(RemoteI18n.t('workspaceTools.runtimeLocal')).width('100%').padding(12) - .backgroundColor(TRANSPARENT).fontColor(INK).onClick(() => this.openDeviceLocation('')) - ForEach(this.toolLocations, (location: SavedRuntimeConnection) => { - Button(location.name || location.id).width('100%').padding(12) - .backgroundColor(TRANSPARENT).fontColor(INK) - .onClick(() => this.openDeviceLocation(location.id)) - }, (location: SavedRuntimeConnection): string => location.id) - }.width('100%').padding(8) - }.width(270).constraintSize({ maxHeight: 360 }).backgroundColor(CARD).borderRadius(16) - } - - private openDeviceLocation(connectionId: string): void { - this.showToolsPicker = false; - if (this.toolsPickerTargetId !== this.controlTargetDeviceId || this.connectionState !== 'connected') return; - this.onWorkspaceTools({ type: 'open-device', deviceId: this.toolsPickerTargetId, connectionId: connectionId, tab: 0 }); + this.onWorkspaceTools({ type: 'open-device', deviceId: this.controlTargetDeviceId, connectionId: '', tab: 0 }); } private shouldShowPrimaryNavigation(): boolean { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatMessageBubble.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatMessageBubble.ets index 5bb66e6383..17faa90d5d 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatMessageBubble.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatMessageBubble.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxState } from '../../model/InteractionMailbox'; import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { ConversationUiImage, ConversationUiMessage, ConversationUiMessageItem, ConversationUiQuestionAnswer, ConversationUiToolStatus } from '../state/ConversationUiModels'; import { RemoteI18n } from '../../i18n/RemoteI18n'; @@ -46,6 +47,7 @@ export struct ChatUserMessageRow { @ReusableV2 @ComponentV2 export struct ChatMessageBubble { + @Param interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); @Param item: ConversationUiMessage = { id: '', role: 'assistant', @@ -337,6 +339,7 @@ export struct ChatMessageBubble { @Builder SubagentGroup(entry: ConversationUiMessageItem, _path: string, activeScope: boolean = false) { SubagentTaskCard({ + interactionMailbox: this.interactionMailbox, title: this.subagentTitle(entry), status: entry.tool?.status || (activeScope ? 'running' : 'completed'), items: entry.subItems || [], @@ -376,6 +379,7 @@ export struct ChatMessageBubble { @Builder Tools(tools: ConversationUiToolStatus[], thinkingParts: ActivityThinkingPart[] = []) { ToolStatusList({ + interactionMailbox: this.interactionMailbox, tools, thinkingParts, renderRevision: this.renderRevision, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatTimeline.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatTimeline.ets index 90fb96ab47..07e486bd07 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatTimeline.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ChatTimeline.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxState } from '../../model/InteractionMailbox'; import { MobileDesignGeometry, MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { ConversationUiMessage, @@ -33,6 +34,7 @@ import { RemoteLogger } from '../../services/RemoteLogger'; @ReusableV2 @ComponentV2 struct ChatAssistantTimelineRow { + @Param interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); @Param row: ObservableChatTimelineItem = new ObservableChatTimelineItem({ id: '', type: 'empty_state', @@ -78,6 +80,7 @@ struct ChatAssistantTimelineRow { // can be thawed before its retained @Local state has been synchronized, // and omitting the child at that point leaves a permanently hollow row. ChatMessageBubble({ + interactionMailbox: this.interactionMailbox, item: this.uiMessage, renderRevision: this.renderRevision, isStreaming: this.row.isStreaming, @@ -110,6 +113,7 @@ struct ChatAssistantTimelineRow { @ComponentV2 export struct ChatTimeline { + @Param interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); private readonly listScroller: Scroller = new Scroller(); private followTimerId: number = 0; private followedActiveTurnKey: string = ''; @@ -123,6 +127,8 @@ export struct ChatTimeline { @Param timelineItems: ObservableChatTimelineItem[] = []; @Param timelineRevision: number = 0; @Param hasMoreMessages: boolean = false; + @Param historyLoading: boolean = false; + @Param historyFailed: boolean = false; @Param isBusy: boolean = false; @Param connectionState: string = 'connected'; @Param statusText: string = ''; @@ -181,14 +187,14 @@ export struct ChatTimeline { List({ space: MobileDesignGeometry.messageSpacing, scroller: this.listScroller }) { if (this.hasMoreMessages) { ListItem() { - Text(this.isBusy ? RemoteI18n.t('common.loading') : RemoteI18n.t('chat.loadOlder')) + Text(this.historyLoading ? RemoteI18n.t('common.loading') : RemoteI18n.t(this.historyFailed ? 'chat.loadOlderFailed' : 'chat.loadOlder')) .fontSize(MobileDesignTypography.bodySmall.size) .fontColor(MUTED) .textAlign(TextAlign.Center) .width('100%') .padding({ top: 10, bottom: 10 }) .onClick(() => { - this.onLoadOlder(); + if (!this.historyLoading) this.onLoadOlder(); }) } } @@ -218,6 +224,7 @@ export struct ChatTimeline { .reuse({ reuseId: () => 'chat_user' }) } else { ChatAssistantTimelineRow({ + interactionMailbox: this.interactionMailbox, row: repeatItem.item, renderRevision: this.timelineRevision, isBusy: this.isBusy, @@ -272,6 +279,7 @@ export struct ChatTimeline { if (item.message) { ListItem() { ChatAssistantTimelineRow({ + interactionMailbox: this.interactionMailbox, row: item, renderRevision: this.timelineRevision, isBusy: this.isBusy, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ConversationView.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ConversationView.ets index c80872080c..e519044e32 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ConversationView.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ConversationView.ets @@ -1,3 +1,5 @@ +import { InteractionMailboxPanel } from './InteractionMailboxPanel'; +import { InteractionMailboxAction } from '../../model/InteractionMailbox'; import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { KeyboardAvoidMode } from '@kit.ArkUI'; import { RemoteI18n } from '../../i18n/RemoteI18n'; @@ -8,7 +10,6 @@ import { ChatComposerCapabilities } from '../state/ChatComposerCapabilities'; import { ChatSurface } from '../state/ChatSurface'; import { ConversationViewState } from '../state/ConversationViewState'; import { ConversationIntent, ConversationIntents, ConversationIntentType } from '../actions/ConversationIntent'; -import { ChatStatusBar } from './ChatStatusBar'; import { ChatTimeline } from './ChatTimeline'; import { ConversationLoadingState } from './ConversationLoadingState'; import { @@ -61,6 +62,7 @@ export struct ConversationView { Stack() { Column() { this.Header() + this.Mailbox() if (this.hoverOperateMode) { this.HoverBody() } else { @@ -106,10 +108,15 @@ export struct ConversationView { } @Builder - private ConversationBody() { - if (this.shouldShowStatusBar()) { - this.ExecutionStatusBar() + private Mailbox() { + if (this.surface === ChatSurface.Remote && this.state.interactionMailbox.sessionId === this.state.activeSession.sessionId) { + InteractionMailboxPanel({ state: this.state.interactionMailbox, + onAction: (action: InteractionMailboxAction): void => this.onIntent(ConversationIntents.mailbox(action)) }) } + } + + @Builder + private ConversationBody() { if (this.shouldCenterInlineStatus()) { this.CenteredInlineStatus() } else { @@ -178,26 +185,16 @@ export struct ConversationView { }) } - @Builder - ExecutionStatusBar() { - ChatStatusBar({ - title: this.taskSummaryTitle(), - detail: this.taskSummaryDetail(), - color: this.statusColor(), - canStop: this.canStop, - onStop: () => { - this.onStop(); - } - }) - } - @Builder MessageList() { ChatTimeline({ + interactionMailbox: this.state.interactionMailbox, surface: this.surface, timelineItems: this.visibleTimelineItems(), timelineRevision: this.timelineRevision, hasMoreMessages: this.hasMoreMessages, + historyLoading: this.state.historyLoading, + historyFailed: this.state.historyFailed, isBusy: this.isBusy || this.isLoadingConversation, connectionState: this.connectionState, statusText: this.statusText, @@ -387,40 +384,6 @@ export struct ConversationView { return ConnectionStatusPresenter.detail(this.connectionState, this.statusText, '', ''); } - private statusColor(): ResourceColor { - if (this.canStop && this.connectionState === 'connected') { - return STATUS_SUCCESS; - } - return this.connectionColor(); - } - - private taskSummaryTitle(): string { - if (this.canStop) { - return RemoteI18n.t('chat.executing'); - } - if (this.connectionState === 'failed') { - return RemoteI18n.t('chat.emptyConnectionError'); - } - if (this.connectionState === 'reconnecting') { - return RemoteI18n.t('chat.emptyReconnecting'); - } - return ConnectionStatusPresenter.label(this.connectionState); - } - - private taskSummaryDetail(): string { - const detail = this.statusLine(); - return detail === this.taskSummaryTitle() ? '' : detail; - } - - private shouldShowStatusBar(): boolean { - return this.surface === ChatSurface.Remote && - !this.isLoadingConversation && - this.connectionState !== 'connected' && - (this.activeSession.sessionId.length > 0 || - this.connectionState === 'failed' || - this.connectionState === 'reconnecting'); - } - private headerTitle(): string { return this.headerPresentation().title; } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/InteractionMailboxPanel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/InteractionMailboxPanel.ets new file mode 100644 index 0000000000..34331610d7 --- /dev/null +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/InteractionMailboxPanel.ets @@ -0,0 +1,88 @@ +import { MobileDesignTypography, MobileDesignGeometry } from '../../generated/MobileDesignTokens'; +import { RemoteI18n } from '../../i18n/RemoteI18n'; +import { + InteractionMailboxAction, InteractionMailboxActionType, InteractionMailboxState, + PendingPermission, PendingQuestion +} from '../../model/InteractionMailbox'; +import { ConversationUiQuestionAnswer, toRemoteQuestionAnswer } from '../state/ConversationUiModels'; +import { ToolConfirmationPanel, ToolQuestionAnswerPanel } from './ToolInteractionPanels'; +import { CARD, INK, LINE, MUTED, SOFT, STATUS_DANGER } from './Theme'; + +@ComponentV2 +export struct InteractionMailboxPanel { + @Param state: InteractionMailboxState = new InteractionMailboxState(); + @Event onAction: (action: InteractionMailboxAction) => void = (_action: InteractionMailboxAction) => {}; + @Local editingId: string = ''; + + build() { + if (this.state.requests.length > 0 || this.state.questions.length > 0 || this.state.failure.length > 0) { + Scroll() { + Column({ space: 12 }) { + if (this.state.failure.length > 0) { + Row({ space: 8 }) { + Text(RemoteI18n.t(this.state.failure === 'load' ? 'chat.interactionLoadFailed' : 'chat.approvalFailed')) + .fontSize(MobileDesignTypography.bodySmall.size).fontColor(STATUS_DANGER).layoutWeight(1) + Button(RemoteI18n.t('common.retry')) + .fontSize(MobileDesignTypography.labelMedium.size).fontColor(INK) + .backgroundColor(SOFT).height(32).borderRadius(16) + .border({ width: 1, color: LINE }) + .enabled(!this.state.busy) + .onClick(() => this.onAction({ sessionId: this.state.sessionId, type: InteractionMailboxActionType.Refresh })) + }.width('100%') + } + ForEach(this.state.questions, (question: PendingQuestion) => { + ToolQuestionAnswerPanel({ + toolId: question.toolId, questionsJson: JSON.stringify(question.questions), isBusy: this.state.busy, + onAnswerQuestion: (id: string, answers: ConversationUiQuestionAnswer): void => { + this.onAction({ sessionId: this.state.sessionId, type: InteractionMailboxActionType.Answer, + id, answers: toRemoteQuestionAnswer(answers) }); + } + }) + .onTouch((event: TouchEvent): void => { + if (event.type === TouchType.Down) this.onAction({ sessionId: this.state.sessionId, + type: InteractionMailboxActionType.StartQuestion, id: question.toolId }); + }) + }, (question: PendingQuestion): string => `${this.state.sessionId}:${question.toolId}`) + ForEach(this.state.requests, (request: PendingPermission) => { + Column({ space: MobileDesignGeometry.approvalCardGap }) { + Row({ space: 8 }) { + // Rasterized from the desktop glyph with the shared warning token (same in both themes). + Image($r('app.media.approval_shield')).width(16).height(16) + Text(request.source?.identity || request.action || RemoteI18n.t('chat.approvalRequired')) + .fontSize(MobileDesignTypography.bodySmall.size).fontColor(INK) + .fontWeight(FontWeight.Medium).layoutWeight(1) + Button(this.editingId === request.requestId ? RemoteI18n.t('chat.hideApprovalInput') : RemoteI18n.t('chat.editApprovalInput')) + .fontSize(MobileDesignTypography.labelSmall.size).fontColor(MUTED) + .backgroundColor(CARD).height(32).padding({ left: 8, right: 8 }) + .enabled(!this.state.busy) + .onClick(() => { this.editingId = this.editingId === request.requestId ? '' : request.requestId; }) + }.width('100%') + Column({ space: 6 }) { + if (request.action && request.source?.identity && request.action.toLowerCase() !== request.source.identity.toLowerCase()) { + Text(request.action).fontSize(MobileDesignTypography.bodySmall.size).fontColor(MUTED) + } + if ((request.resources || []).length > 0) { + Text((request.resources || []).join('\n')) + .fontSize(MobileDesignTypography.labelSmall.size).fontFamily('monospace').fontColor(INK) + .width('100%').padding(10).backgroundColor(SOFT).borderRadius(8) + } + }.width('100%').alignItems(HorizontalAlign.Start) + ToolConfirmationPanel({ + toolId: request.requestId, defaultInputText: '{}', leadingInset: 0, compactActions: true, + hasEditableInput: this.editingId === request.requestId, isBusy: this.state.busy, + onApproveTool: (id: string, updatedInput?: Object): void => { + this.onAction({ sessionId: this.state.sessionId, type: InteractionMailboxActionType.Approve, id, updatedInput }); + }, + onRejectTool: (id: string): void => { + this.onAction({ sessionId: this.state.sessionId, type: InteractionMailboxActionType.Reject, id }); + } + }) + }.width('100%').padding(MobileDesignGeometry.approvalCardPadding).alignItems(HorizontalAlign.Start) + .backgroundColor(CARD).borderRadius(MobileDesignGeometry.approvalCardRadius) + .border({ width: 1, color: LINE }) + }, (request: PendingPermission): string => `${this.state.sessionId}:${request.requestId}`) + }.width('100%').padding({ left: 16, right: 16, top: 8, bottom: 8 }) + }.width('100%').constraintSize({ maxHeight: '40%' }).scrollBar(BarState.Auto) + } + } +} diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/MarkdownContent.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/MarkdownContent.ets index 74db99d88c..c2b1e05a48 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/MarkdownContent.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/MarkdownContent.ets @@ -52,7 +52,7 @@ export struct MarkdownContent { .padding({ left: 9 + Math.max(0, block.level - 1) * 10 }) .border({ width: { left: 2 }, color: LINE }) } else if (block.type === 'list') { - this.MarkdownList(block.items) + this.MarkdownList(block.items, this.listMarkerWidth(block.items)) } else if (block.type === 'table') { this.MarkdownTable(block.tableRows) } else if (block.type === 'divider') { @@ -131,8 +131,22 @@ export struct MarkdownContent { } } + private listMarkerWidth(items: ParsedMarkdownListItem[]): number { + const context = this.getUIContext(); + let width: number = 20; + for (const item of items) { + if (item.checked >= 0) continue; + const size = context.getMeasureUtils().measureTextSize({ + textContent: item.marker, + fontSize: `${MobileDesignTypography.bodyLarge.size}fp` + }); + width = Math.max(width, Math.ceil(context.px2vp(Number(size.width)))); + } + return width; + } + @Builder - MarkdownList(items: ParsedMarkdownListItem[]) { + MarkdownList(items: ParsedMarkdownListItem[], markerWidth: number) { Column({ space: 4 }) { ForEach(items, (item: ParsedMarkdownListItem) => { Row({ space: 7 }) { @@ -151,7 +165,9 @@ export struct MarkdownContent { .borderRadius(4) } else { Text(item.marker) - .width(20) + .width(markerWidth) + .maxLines(1) + .flexShrink(0) .fontSize(MobileDesignTypography.bodyLarge.size) .lineHeight(MobileDesignTypography.bodyLarge.lineHeight) .fontColor(MUTED) diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/RecentRemoteHome.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/RecentRemoteHome.ets index 55022a2d3a..c0b5f94ef5 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/RecentRemoteHome.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/RecentRemoteHome.ets @@ -1,21 +1,31 @@ import { RemoteI18n } from '../../i18n/RemoteI18n'; import { MobileDesignGeometry, MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { WelcomeBrandFlow } from './WelcomeBrandFlow'; -import { INK, MUTED, SOFT } from './Theme'; +import { RemoteCompactHomePolicy } from '../policy/RemoteCompactHomePolicy'; +import { RemoteSession } from '../../model/RemoteModels'; +import { INK, MUTED, SOFT, LINE, TRANSPARENT } from './Theme'; /** Current-device landing page. Session activation stays with the remote owner. */ @ComponentV2 export struct RecentRemoteHome { @Param connected: boolean = false; @Param busy: boolean = false; + @Param sessions: RemoteSession[] = []; + @Param desktopName: string = ''; + @Event onOpen: (session: RemoteSession) => void = () => {}; @Event onBrowse: () => void = () => {}; @Event onConnect: () => void = () => {}; + private sessionContext(session: RemoteSession): string { + const workspace = session.workspaceName || (session.workspacePath || '').replace(/\/+$/, '').split('/').pop() || ''; + return [this.desktopName, workspace].filter((value: string): boolean => value.length > 0).join(' ยท '); + } + build() { Scroll() { Column() { Column() { - WelcomeBrandFlow({ markSize: 156, sweep: true }) + WelcomeBrandFlow({ markSize: MobileDesignGeometry.recentHomeMarkSize, sweep: true }) .margin({ top: 20, bottom: 8 }) Text(RemoteI18n.t('home.recent_title')).fontSize(MobileDesignTypography.headlineLarge.size) .fontWeight(FontWeight.Medium).fontColor(INK).textAlign(TextAlign.Center) @@ -23,14 +33,33 @@ export struct RecentRemoteHome { if (this.busy) { LoadingProgress().width(24).height(24).color(MUTED).margin({ bottom: 16 }) } - if (!this.connected) { + if (!RemoteCompactHomePolicy.shouldShowRecentSessions(this.connected, this.sessions.length)) { Text(RemoteI18n.t('home.recent_connect')).fontSize(MobileDesignTypography.bodySmall.size).fontColor(MUTED).margin({ bottom: 16 }) Button(RemoteI18n.t('home.connectComputer')).onClick(this.onConnect) .backgroundColor(SOFT).fontColor(INK).height(48).width('100%').margin({ bottom: 16 }) } - if (this.connected) { - Button(RemoteI18n.t('sidebar.workspaces')).onClick(this.onBrowse) - .backgroundColor(SOFT).fontColor(INK).height(48).width('100%') + if (RemoteCompactHomePolicy.shouldShowRecentSessions(this.connected, this.sessions.length)) { + Row() { + Text(RemoteI18n.t('home.recent_recent')).fontSize(MobileDesignTypography.labelSmall.size).fontColor(MUTED).layoutWeight(1) + Button(RemoteI18n.t('home.recent_all')).fontSize(MobileDesignTypography.labelSmall.size).fontColor(INK) + .backgroundColor(TRANSPARENT).height(44).onClick(this.onBrowse) + }.width('100%') + ForEach(this.sessions, (session: RemoteSession) => { + Column({ space: 6 }) { + Text(session.title).fontSize(MobileDesignTypography.titleSmall.size).fontColor(INK).maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%') + Text(this.sessionContext(session)).fontSize(MobileDesignTypography.labelSmall.size).fontColor(MUTED).maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%') + }.width('100%').alignItems(HorizontalAlign.Start) + .padding({ top: MobileDesignGeometry.recentHomeRowPadding, bottom: MobileDesignGeometry.recentHomeRowPadding }) + .onClick(() => { if (this.connected && !this.busy) this.onOpen(session); }) + .accessibilityGroup(true).accessibilityText(session.title) + Divider().color(LINE) + }, (session: RemoteSession): string => session.id) + if (this.sessions.length === 0 && !this.busy) { + Text(RemoteI18n.t('home.recent_empty')).fontSize(MobileDesignTypography.bodySmall.size).fontColor(MUTED) + .padding({ top: 18, bottom: 18 }) + } } }.alignItems(HorizontalAlign.Start).width('100%') diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets index 7f8f540ad3..775f48ff19 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarDeviceGroup.ets @@ -1,3 +1,4 @@ +import { remoteWorkspaceKey, remoteSessionBelongsToWorkspace } from '../../services/RemoteSessionIdentity'; import { WorkspaceToolsIntent } from '../state/WorkspaceToolsState'; import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { RecentWorkspaceEntry, RemoteSession } from '../../model/RemoteModels'; @@ -32,6 +33,7 @@ import { */ @ComponentV2 struct SidebarWorkspaceGroup { + @Param sshHost: string = ''; @Param connectionId: string = ''; @Event onWorkspaceTools: (intent: WorkspaceToolsIntent) => void = (_intent: WorkspaceToolsIntent) => {}; @Param deviceId: string = ''; @@ -47,11 +49,11 @@ struct SidebarWorkspaceGroup { @Param loadStatus: WorkspaceDirectoryStatus = 'idle'; @Param hasLoadedSessions: boolean = false; @Param supportsHarnessProfiles: boolean = false; - @Event onOpenWorkspace: (path: string) => void = (_path: string) => {}; + @Event onOpenWorkspace: (path: string, connectionId?: string, sshHost?: string) => void = (_path: string) => {}; @Event onExpandWorkspace: (path: string) => Promise = async (_path: string) => {}; @Event onExpandedChange: (path: string, expanded: boolean) => void = (_path: string, _expanded: boolean) => {}; - @Event onCreateInWorkspace: (path: string, agentType: string, connectionId?: string) => void = + @Event onCreateInWorkspace: (path: string, agentType: string, connectionId?: string, sshHost?: string) => void = (_path: string, _agentType: string) => {}; @Event onOpenSession: (session: RemoteSession) => void = (_session: RemoteSession) => {}; @Event onSessionActions: (session: RemoteSession) => void = (_session: RemoteSession) => {}; @@ -112,6 +114,7 @@ struct SidebarWorkspaceGroup { .layoutWeight(1) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) + .wordBreak(WordBreak.BREAK_ALL) .hitTestBehavior(HitTestMode.None) } .layoutWeight(1) @@ -121,7 +124,7 @@ struct SidebarWorkspaceGroup { void this.toggleExpanded(); }) .gesture(LongPressGesture({ repeat: false }).onAction(() => { - this.onOpenWorkspace(this.path); + this.onOpenWorkspace(this.path, this.connectionId, this.sshHost); })) Stack({ alignContent: Alignment.Center }) { @@ -164,7 +167,7 @@ struct SidebarWorkspaceGroup { private CreateModeMenu() { HarnessProfileMenu({ showTitle: false, onSelect: (agentType: string) => { this.showCreateMenu = false; - this.onCreateInWorkspace(this.path, agentType, this.connectionId); + this.onCreateInWorkspace(this.path, agentType, this.connectionId, this.sshHost); } }) } @@ -225,12 +228,13 @@ struct SidebarWorkspaceGroup { .width(19) .height(19) Text(item.title || RemoteI18n.t('sidebar.untitled')) - .fontSize(MobileDesignTypography.titleSmall.size) - .fontWeight(item.id === this.selectedSessionId ? FontWeight.Medium : FontWeight.Regular) + .fontSize(MobileDesignTypography.bodySmall.size) + .fontWeight(item.id === this.selectedSessionId ? FontWeight.Bold : FontWeight.Regular) .fontColor(INK) .layoutWeight(1) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) + .wordBreak(WordBreak.BREAK_ALL) } .width('100%') .height(44) @@ -328,6 +332,8 @@ export struct SidebarDeviceGroup { @Param recentWorkspaces: RecentWorkspaceEntry[] = []; @Param workspaceName: string = ''; @Param workspacePath: string = ''; + @Param workspaceConnectionId: string = ''; + @Param workspaceSshHost: string = ''; @Param workspaceKind: string = 'normal'; @Param selectedSessionId: string = ''; @Param runningSessionId: string = ''; @@ -339,11 +345,11 @@ export struct SidebarDeviceGroup { @Event onRetry: () => void = () => {}; @Event onOpenSession: (session: RemoteSession) => void = (_session: RemoteSession) => {}; @Event onSessionActions: (session: RemoteSession) => void = (_session: RemoteSession) => {}; - @Event onCreateInWorkspace: (path: string, agentType: string, connectionId?: string) => void = + @Event onCreateInWorkspace: (path: string, agentType: string, connectionId?: string, sshHost?: string) => void = (_path: string, _agentType: string) => {}; - @Event onOpenWorkspace: (path: string) => void = (_path: string) => {}; - @Event onExpandWorkspace: (path: string) => Promise = async (_path: string) => {}; - @Event onWorkspaceExpandedChange: (path: string, expanded: boolean) => void = + @Event onOpenWorkspace: (path: string, connectionId?: string, sshHost?: string) => void = (_path: string) => {}; + @Event onExpandWorkspace: (path: string, remoteConnectionId?: string, remoteSshHost?: string) => Promise = async (_path: string) => {}; + @Event onWorkspaceExpandedChange: (path: string, expanded: boolean, remoteConnectionId?: string, remoteSshHost?: string) => void = (_path: string, _expanded: boolean) => {}; @Local visibleWorkspaceCount: number = SidebarDirectoryPreviewPolicy.PREVIEW_COUNT; private readonly projectionCache: SessionListProjectionCache = new SessionListProjectionCache(); @@ -368,29 +374,30 @@ export struct SidebarDeviceGroup { ForEach(this.visibleWorkspaceEntries(), (entry: RecentWorkspaceEntry) => { SidebarWorkspaceGroup({ connectionId: entry.remoteConnectionId || '', + sshHost: entry.remoteSshHost || '', onWorkspaceTools: this.onWorkspaceTools, deviceId: this.device.deviceId, path: entry.path, name: entry.remoteSshHost?.trim() ? `${entry.name} ยท ${entry.remoteSshHost.trim()}` : entry.name, - sessions: this.sessionsFor(entry.path), + sessions: this.sessionsFor(entry), selectedSessionId: this.selectedSessionId, runningSessionId: this.runningSessionId, - currentWorkspace: this.isCurrentWorkspace(entry.path), + currentWorkspace: this.isCurrentWorkspace(entry), activeDevice: this.isActive, supportsHarnessProfiles: this.supportsHarnessProfiles, bodyIndent: this.bodyIndent(), - expanded: this.directoryState.workspaceExpanded(this.device.deviceId, entry.path), - loadStatus: this.directoryState.workspaceStatus(this.device.deviceId, entry.path), - hasLoadedSessions: this.directoryState.workspaceHasLoadedSessions(this.device.deviceId, entry.path), + expanded: this.directoryState.workspaceExpanded(this.device.deviceId, entry.path, entry.remoteConnectionId, entry.remoteSshHost), + loadStatus: this.directoryState.workspaceStatus(this.device.deviceId, entry.path, entry.remoteConnectionId, entry.remoteSshHost), + hasLoadedSessions: this.directoryState.workspaceHasLoadedSessions(this.device.deviceId, entry.path, entry.remoteConnectionId, entry.remoteSshHost), onOpenWorkspace: this.onOpenWorkspace, - onExpandWorkspace: this.onExpandWorkspace, - onExpandedChange: this.onWorkspaceExpandedChange, + onExpandWorkspace: (path: string): Promise => this.onExpandWorkspace(path, entry.remoteConnectionId, entry.remoteSshHost), + onExpandedChange: (path: string, expanded: boolean): void => this.onWorkspaceExpandedChange(path, expanded, entry.remoteConnectionId, entry.remoteSshHost), onCreateInWorkspace: this.onCreateInWorkspace, onOpenSession: this.onOpenSession, onSessionActions: this.onSessionActions }) }, (entry: RecentWorkspaceEntry): string => - SidebarDirectoryPreviewPolicy.workspaceKey(this.device.deviceId, entry.path)) + `${this.device.deviceId}:${remoteWorkspaceKey(entry)}`) if (this.hiddenWorkspaceCount() > 0) { this.MoreWorkspacesRow() } @@ -415,12 +422,13 @@ export struct SidebarDeviceGroup { .height(21) .hitTestBehavior(HitTestMode.None) Text(this.device.deviceName) - .fontSize(MobileDesignTypography.titleSmall.size) - .fontWeight(FontWeight.Medium) + .fontSize(MobileDesignTypography.bodyMedium.size) + .fontWeight(this.isActive ? FontWeight.Bold : FontWeight.Regular) .fontColor(this.device.online ? INK : SUBTLE) .layoutWeight(1) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) + .wordBreak(WordBreak.BREAK_ALL) .hitTestBehavior(HitTestMode.None) if (!this.device.online) { Text(RemoteI18n.t('sidebar.deviceOffline')) @@ -560,36 +568,9 @@ export struct SidebarDeviceGroup { } private workspaceEntries(): RecentWorkspaceEntry[] { - // The shared projection intentionally removes assistant workspaces because - // the desktop conversation sidebar presents those sessions as top-level - // chats. The device directory has a different information architecture: - // every session belongs below its workspace, including Claw/assistant - // workspaces. Reintroduce the complete device catalog here without changing - // the shared desktop projection contract. - const entries: RecentWorkspaceEntry[] = []; - this.projection().projects.forEach((item: RecentWorkspaceEntry): void => { - if (!entries.some((entry: RecentWorkspaceEntry): boolean => - ConversationSessionFilterPolicy.workspacePathsEqual(entry.path, item.path))) { - entries.push(item); - } - }); - if (this.workspacePath.length > 0 && - !entries.some((item: RecentWorkspaceEntry): boolean => - ConversationSessionFilterPolicy.workspacePathsEqual(item.path, this.workspacePath))) { - entries.unshift({ - path: this.workspacePath, - name: this.workspaceName, - lastOpened: '', - workspaceKind: this.workspaceKind - }); - } - this.recentWorkspaces.forEach((item: RecentWorkspaceEntry) => { - if (item.path.length > 0 && - !entries.some((entry: RecentWorkspaceEntry): boolean => - ConversationSessionFilterPolicy.workspacePathsEqual(entry.path, item.path))) { - entries.push(item); - } - }); + // Device catalog membership is authoritative, even when empty. Selection + // and historical sessions cannot reopen a workspace in the sidebar. + const entries = this.recentWorkspaces.slice(); const query = this.query.trim().toLowerCase(); if (query.length === 0) { return entries; @@ -597,7 +578,7 @@ export struct SidebarDeviceGroup { return entries.filter((entry: RecentWorkspaceEntry): boolean => { const label = (entry.name || this.basename(entry.path)).toLowerCase(); return label.indexOf(query) >= 0 || entry.path.toLowerCase().indexOf(query) >= 0 || - this.sessionsFor(entry.path).length > 0; + this.sessionsFor(entry).length > 0; }); } @@ -615,16 +596,15 @@ export struct SidebarDeviceGroup { ); } - private sessionsFor(path: string): RemoteSession[] { - const target = ConversationSessionFilterPolicy.normalizeWorkspacePath(path); - return this.projection().filtered.filter((item: RemoteSession): boolean => { - const itemPath = item.workspacePath ? item.workspacePath : this.workspacePath; - return ConversationSessionFilterPolicy.normalizeWorkspacePath(itemPath) === target; - }); + private sessionsFor(entry: RecentWorkspaceEntry): RemoteSession[] { + return this.projection().filtered.filter((item: RemoteSession): boolean => + remoteSessionBelongsToWorkspace(item, entry, this.workspaceEntries())); } - private isCurrentWorkspace(path: string): boolean { - return this.isActive && path.length > 0 && path === this.workspacePath; + private isCurrentWorkspace(entry: RecentWorkspaceEntry): boolean { + return this.isActive && entry.path.length > 0 && remoteWorkspaceKey(entry) === remoteWorkspaceKey({ + path: this.workspacePath, remoteConnectionId: this.workspaceConnectionId, remoteSshHost: this.workspaceSshHost + }); } private basename(path: string): string { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets index eee973a5bd..7765902524 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SidebarWorkspaceSection.ets @@ -16,13 +16,14 @@ import { SettingsSheetKind } from '../policy/SettingsPlacementPolicy'; import { AdaptiveSheetOptions } from './AdaptiveSheetOptions'; -import { STATUS_SUCCESS, INK, MUTED, SUBTLE } from './Theme'; +import { STATUS_SUCCESS, STATUS_DANGER, INK, MUTED, SUBTLE } from './Theme'; import { SidebarDeviceGroup } from './SidebarDeviceGroup'; import { SidebarChevronIcon, SidebarDeviceIcon } from './SidebarNavigationIcons'; import { SidebarWorkspacePicker } from './SidebarWorkspacePicker'; +import { ConnectionStatusPresenter } from '../../services/ConnectionStatusPresenter'; import { RemoteUiState } from '../../services/RemoteUiState'; /** @@ -43,6 +44,8 @@ export struct SidebarWorkspaceSection { @Param recentWorkspaces: RecentWorkspaceEntry[] = []; @Param workspaceName: string = ''; @Param workspacePath: string = ''; + @Param workspaceConnectionId: string = ''; + @Param workspaceSshHost: string = ''; @Param workspaceKind: string = 'normal'; @Param connectionState: string = 'idle'; @Param controlTargetType: string = 'none'; @@ -56,13 +59,14 @@ export struct SidebarWorkspaceSection { SettingsPlacementPolicy.compactBottom(SettingsSheetKind.SessionDetails); @Event onOpenSession: (session: RemoteSession) => void = (_session: RemoteSession) => {}; @Event onSessionActions: (session: RemoteSession) => void = (_session: RemoteSession) => {}; - @Event onCreateInWorkspace: (deviceId: string, path: string, agentType: string, connectionId?: string) => void = + @Event onCreateInWorkspace: (deviceId: string, path: string, agentType: string, connectionId?: string, sshHost?: string) => void = (_deviceId: string, _path: string, _agentType: string) => {}; - @Event onOpenWorkspace: (deviceId: string, path: string, connectionId?: string) => void = + @Event onOpenWorkspace: (deviceId: string, path: string, connectionId?: string, sshHost?: string) => void = (_deviceId: string, _path: string) => {}; - @Event onExpandWorkspace: (deviceId: string, path: string) => Promise = + @Event onExpandWorkspace: (deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string) => Promise = async (_deviceId: string, _path: string) => {}; @Event onSelectDevice: (deviceId: string) => void = (_deviceId: string) => {}; + @Event onReconnect: () => void = () => {}; @Event onRetryDevice: (deviceId: string) => void = (_deviceId: string) => {}; @Event onAddWorkspace: (deviceId: string) => void = (_deviceId: string) => {}; @Event onRefreshDevices: () => Promise = async () => {}; @@ -70,12 +74,19 @@ export struct SidebarWorkspaceSection { @Local visibleDeviceCount: number = SidebarDirectoryPreviewPolicy.PREVIEW_COUNT; @Local showWorkspacePicker: boolean = false; @Local workspacePickerDeviceId: string = ''; - @Local refreshingDevices: boolean = false; private readonly transientEntry: DeviceDirectoryEntry = emptyDeviceDirectoryEntry('', '', false); build() { Column() { this.DeviceSectionHeader() + if (this.directoryState.refreshError.length > 0) { + Text(this.directoryState.refreshError) + .fontSize(MobileDesignTypography.bodySmall.size) + .fontColor(STATUS_DANGER) + .width('100%') + .padding({ top: 4, bottom: 8 }) + .onClick(() => { void this.onRefreshDevices(); }) + } Row() {}.height(0) .bindSheet($$this.workspaceTools.visible, this.WorkspaceTools(), { height: SheetSize.LARGE, onWillDismiss: (action: DismissSheetAction): void => { if (this.workspaceTools.dirty) this.onWorkspaceTools({ type: 'close' }); @@ -84,6 +95,10 @@ export struct SidebarWorkspaceSection { if (this.deviceEntries().length > 0) { ForEach(this.visibleDeviceEntries(), (entry: DeviceDirectoryEntry) => { this.DeviceRow(entry) + if (this.isActiveDevice(entry.deviceId) && + (this.connectionState === 'reconnecting' || this.connectionState === 'failed' || this.connectionState === 'disconnected')) { + this.ConnectionRecoveryRow() + } }, (entry: DeviceDirectoryEntry): string => this.deviceKey(entry)) if (this.visibleDeviceCount < this.deviceEntries().length) { this.MoreDevicesRow() @@ -117,7 +132,7 @@ export struct SidebarWorkspaceSection { .fontColor(MUTED) Blank() Stack({ alignContent: Alignment.Center }) { - if (this.refreshingDevices) { + if (this.directoryState.refreshing) { LoadingProgress() .width(18) .height(18) @@ -131,23 +146,12 @@ export struct SidebarWorkspaceSection { } .width(34) .height(38) - .enabled(!this.refreshingDevices) + .enabled(!this.directoryState.refreshing) .accessibilityText(RemoteI18n.t('common.refresh')) .onClick(() => { - void this.refreshDevices(); - }) - Stack({ alignContent: Alignment.Center }) { - SymbolGlyph($r('sys.symbol.plus')) - .fontSize(20) - .fontWeight(FontWeight.Medium) - .fontColor([INK]) - } - .width(34) - .height(38) - .accessibilityText(RemoteI18n.t('sidebar.addConnection')) - .onClick(() => { - this.onAddDevice(); + void this.onRefreshDevices(); }) + } .width('100%') .height(42) @@ -164,12 +168,13 @@ export struct SidebarWorkspaceSection { }) .hitTestBehavior(HitTestMode.None) Text(entry.deviceName) - .fontSize(MobileDesignTypography.titleSmall.size) - .fontWeight(this.isSelected(entry) ? FontWeight.Medium : FontWeight.Regular) + .fontSize(MobileDesignTypography.bodyMedium.size) + .fontWeight(entry.deviceId.length > 0 && this.controlTargetDeviceId.trim() === entry.deviceId ? FontWeight.Bold : FontWeight.Regular) .fontColor(entry.online ? INK : SUBTLE) .layoutWeight(1) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) + .wordBreak(WordBreak.BREAK_ALL) .hitTestBehavior(HitTestMode.None) if (entry.status === 'loading') { LoadingProgress() @@ -181,7 +186,7 @@ export struct SidebarWorkspaceSection { Text('') .width(8) .height(8) - .backgroundColor(entry.online ? STATUS_SUCCESS : SUBTLE) + .backgroundColor(this.deviceHealthColor(entry)) .borderRadius(4) .hitTestBehavior(HitTestMode.None) } @@ -202,8 +207,8 @@ export struct SidebarWorkspaceSection { .padding({ left: 8, right: 4 }) .alignItems(VerticalAlign.Center) .opacity(entry.online ? 1 : 0.58) - .accessibilityText(`${entry.deviceName}, ${RemoteI18n.t(entry.online ? - 'sidebar.deviceOnline' : 'sidebar.deviceOffline')}`) + .accessibilityText(`${entry.deviceName}, ${this.isActiveDevice(entry.deviceId) ? ConnectionStatusPresenter.label(this.connectionState) : + RemoteI18n.t(entry.online ? 'sidebar.deviceOnline' : 'sidebar.deviceOffline')}`) .enabled(this.canUseDevice(entry)) .onClick(() => { if (this.canUseDevice(entry)) { @@ -212,6 +217,30 @@ export struct SidebarWorkspaceSection { }) } + private deviceHealthColor(entry: DeviceDirectoryEntry): ResourceColor { + if (!this.isActiveDevice(entry.deviceId)) return entry.online ? STATUS_SUCCESS : SUBTLE; + if (this.connectionState === 'connected') return STATUS_SUCCESS; + if (this.connectionState === 'failed' || this.connectionState === 'disconnected') return STATUS_DANGER; + return MUTED; + } + + @Builder + private ConnectionRecoveryRow() { + Row() { + Text(ConnectionStatusPresenter.label(this.connectionState)) + .fontSize(MobileDesignTypography.labelSmall.size) + .fontColor(MUTED) + .layoutWeight(1) + Stack({ alignContent: Alignment.Center }) { + SymbolGlyph($r('sys.symbol.arrow_clockwise')).fontSize(17).fontColor([MUTED]) + } + .width(44).height(44) + .accessibilityText(RemoteI18n.t('common.retry')) + .onClick(() => this.onReconnect()) + } + .width('100%').height(44).padding({ left: 10, right: 6 }) + } + @Builder private WorkspaceSection(entry: DeviceDirectoryEntry) { Column() { @@ -241,6 +270,12 @@ export struct SidebarWorkspaceSection { .margin({ top: 16 }) .alignItems(VerticalAlign.Center) + if (entry.catalogSource === 'recent') { + Text(RemoteI18n.t('sidebar.legacyWorkspaceCatalog')) + .fontSize(MobileDesignTypography.bodySmall.size) + .fontColor(MUTED) + .padding({ left: 10, right: 10, bottom: 4 }) + } SidebarDeviceGroup({ onWorkspaceTools: this.onWorkspaceTools, device: entry, @@ -249,6 +284,8 @@ export struct SidebarWorkspaceSection { recentWorkspaces: this.workspacesForDevice(entry), workspaceName: this.liveWorkspaceName(entry), workspacePath: this.liveWorkspacePath(entry), + workspaceConnectionId: this.isActiveDevice(entry.deviceId) ? this.workspaceConnectionId : '', + workspaceSshHost: this.isActiveDevice(entry.deviceId) ? this.workspaceSshHost : '', workspaceKind: this.liveWorkspaceKind(entry), selectedSessionId: this.selectedSessionIdFor(entry), runningSessionId: this.isActiveDevice(entry.deviceId) ? this.runningSessionId : '', @@ -263,17 +300,17 @@ export struct SidebarWorkspaceSection { }, onOpenSession: this.onOpenSession, onSessionActions: this.onSessionActions, - onCreateInWorkspace: (path: string, agentType: string, connectionId?: string) => { - this.onCreateInWorkspace(entry.deviceId, path, agentType, connectionId); + onCreateInWorkspace: (path: string, agentType: string, connectionId?: string, sshHost?: string) => { + this.onCreateInWorkspace(entry.deviceId, path, agentType, connectionId, sshHost); }, - onOpenWorkspace: (path: string) => { - this.onOpenWorkspace(entry.deviceId, path); + onOpenWorkspace: (path: string, connectionId?: string, sshHost?: string) => { + this.onOpenWorkspace(entry.deviceId, path, connectionId, sshHost); }, - onExpandWorkspace: async (path: string): Promise => { - await this.onExpandWorkspace(entry.deviceId, path); + onExpandWorkspace: async (path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise => { + await this.onExpandWorkspace(entry.deviceId, path, remoteConnectionId, remoteSshHost); }, - onWorkspaceExpandedChange: (path: string, expanded: boolean): void => { - this.directoryState.setWorkspaceExpanded(entry.deviceId, path, expanded); + onWorkspaceExpandedChange: (path: string, expanded: boolean, remoteConnectionId?: string, remoteSshHost?: string): void => { + this.directoryState.setWorkspaceExpanded(entry.deviceId, path, expanded, remoteConnectionId, remoteSshHost); } }) } @@ -324,17 +361,18 @@ export struct SidebarWorkspaceSection { onBrowse: this.onWorkspaceTools, deviceName: this.workspacePickerDevice().deviceName, savedConnections: this.workspacePickerDevice().deviceId === this.savedConnectionsTargetId ? this.savedConnections : [], - workspaces: this.workspacesForDevice(this.workspacePickerDevice()), + workspaces: this.workspacePickerDevice().catalogSource.length > 0 ? + this.workspacePickerDevice().recentWorkspaces : this.workspacesForDevice(this.workspacePickerDevice()), selectedWorkspacePath: this.isActiveDevice(this.workspacePickerDevice().deviceId) ? this.workspacePath : '', loading: this.workspacePickerDevice().status === 'loading', onClose: () => { this.showWorkspacePicker = false; }, - onSelectWorkspace: (path: string, connectionId?: string) => { + onSelectWorkspace: (path: string, connectionId?: string, sshHost?: string) => { const deviceId = this.workspacePickerDeviceId; this.showWorkspacePicker = false; - this.onOpenWorkspace(deviceId, path, connectionId); + this.onOpenWorkspace(deviceId, path, connectionId, sshHost); } }) } @@ -420,7 +458,7 @@ export struct SidebarWorkspaceSection { } private workspacesForDevice(entry: DeviceDirectoryEntry): RecentWorkspaceEntry[] { - return this.usesLiveDirectory(entry) ? this.recentWorkspaces : entry.workspaces; + return entry.workspaces; } private selectedSessionIdFor(entry: DeviceDirectoryEntry): string { @@ -446,28 +484,6 @@ export struct SidebarWorkspaceSection { return entry.online || (this.isActiveDevice(entry.deviceId) && this.isConnected()); } - private async refreshDevices(): Promise { - if (this.refreshingDevices) { - return; - } - this.refreshingDevices = true; - const startedAt = Date.now(); - try { - await this.onRefreshDevices(); - } finally { - const remainingMs = 360 - (Date.now() - startedAt); - if (remainingMs > 0) { - await this.delay(remainingMs); - } - this.refreshingDevices = false; - } - } - - private delay(durationMs: number): Promise { - return new Promise((resolve: () => void): void => { - setTimeout(resolve, durationMs); - }); - } private isConnected(): boolean { return this.connectionState === 'connected' || this.connectionState === 'reconnecting'; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SubagentTaskCard.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SubagentTaskCard.ets index f0c8eacb8b..5230524942 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SubagentTaskCard.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/SubagentTaskCard.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxState } from '../../model/InteractionMailbox'; import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { RemoteI18n } from '../../i18n/RemoteI18n'; import { ConversationUiMessageItem, ConversationUiQuestionAnswer } from '../state/ConversationUiModels'; @@ -17,6 +18,7 @@ import { ToolGlyph } from './ToolGlyphs'; @ComponentV2 export struct SubagentTaskCard { + @Param interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); @Param title: string = ''; @Param status: string = 'completed'; @Param items: ConversationUiMessageItem[] = []; @@ -116,6 +118,7 @@ export struct SubagentTaskCard { }) } else if (entry.tool) { ToolStatusList({ + interactionMailbox: this.interactionMailbox, tools: [entry.tool], renderRevision: this.renderRevision, isBusy: this.isRunning(), diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets index 9118db74cf..5d523840b3 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets @@ -31,6 +31,7 @@ export const SOFT: ResourceColor = $r('app.color.soft'); export const FLOATING_PANEL_BG: ResourceColor = $r('app.color.floating_panel_bg'); export const STATUS_SUCCESS: ResourceColor = $r('app.color.status_success'); export const STATUS_DANGER: ResourceColor = $r('app.color.status_danger'); +export const STATUS_WARNING: ResourceColor = $r('app.color.status_warning'); export const CODE_LINE_NUMBER: ResourceColor = $r('app.color.code_line_number'); export const CODE_KEYWORD: ResourceColor = $r('app.color.code_keyword'); export const CODE_STRING: ResourceColor = $r('app.color.code_string'); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolInteractionPanels.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolInteractionPanels.ets index 7eefbd01df..fee8e65101 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolInteractionPanels.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolInteractionPanels.ets @@ -1,7 +1,7 @@ -import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; +import { MobileDesignTypography, MobileDesignGeometry } from '../../generated/MobileDesignTokens'; import { RemoteI18n } from '../../i18n/RemoteI18n'; import { ConversationUiQuestionAnswer } from '../state/ConversationUiModels'; -import { ACCENT, CARD, INK, LINE, MUTED, CONTENT_ON_ACTION, STATUS_DANGER, SOFT } from './Theme'; +import { ACCENT, PRIMARY_ACTION, CARD, INK, LINE, MUTED, CONTENT_ON_ACTION, STATUS_DANGER, SOFT } from './Theme'; interface QuestionOption { label: string; @@ -24,6 +24,9 @@ export struct ToolConfirmationPanel { @Param toolId: string = ''; @Param defaultInputText: string = ''; @Param hasEditableInput: boolean = false; + @Param leadingInset: number = 30; + @Param compactActions: boolean = true; + @Param isBusy: boolean = false; @Event onApproveTool: (toolId: string, updatedInput?: Object) => void = (_toolId: string, _updatedInput?: Object) => {}; @Event onRejectTool: (toolId: string) => void = (_toolId: string) => {}; @@ -39,6 +42,22 @@ export struct ToolConfirmationPanel { if (this.hasEditableInput) { this.InputEditor() } + if (this.compactActions) { + Row({ space: MobileDesignGeometry.approvalCardGap }) { + Blank() + Button(RemoteI18n.t('chat.reject')) + .fontSize(MobileDesignTypography.labelMedium.size).fontColor(INK) + .height(MobileDesignGeometry.approvalActionHeight).padding({ left: 16, right: 16 }) + .backgroundColor(SOFT).borderRadius(MobileDesignGeometry.approvalActionRadius) + .enabled(!this.isBusy) + .onClick(() => this.onRejectTool(this.toolId)) + Button(RemoteI18n.t('chat.approve')) + .fontSize(MobileDesignTypography.labelMedium.size).fontColor(CONTENT_ON_ACTION) + .height(MobileDesignGeometry.approvalActionHeight).padding({ left: 16, right: 16 }) + .backgroundColor(PRIMARY_ACTION).borderRadius(MobileDesignGeometry.approvalActionRadius) + .enabled(!this.isBusy).onClick(() => this.approve()) + }.width('100%') + } else { Row({ space: 8 }) { Text(RemoteI18n.t('chat.approve')) .fontSize(MobileDesignTypography.labelMedium.size) @@ -48,6 +67,7 @@ export struct ToolConfirmationPanel { .layoutWeight(1) .backgroundColor(ACCENT) .borderRadius(16) + .enabled(!this.isBusy) .onClick(() => this.approve()) Text(RemoteI18n.t('chat.reject')) .fontSize(MobileDesignTypography.labelMedium.size) @@ -58,12 +78,14 @@ export struct ToolConfirmationPanel { .backgroundColor(SOFT) .borderRadius(16) .border({ width: 1, color: LINE }) + .enabled(!this.isBusy) .onClick(() => this.onRejectTool(this.toolId)) } .width('100%') + } } .width('100%') - .padding({ left: 30 }) + .padding({ left: this.leadingInset }) } @Builder @@ -91,7 +113,7 @@ export struct ToolConfirmationPanel { .padding(10) .border({ width: 1, color: this.inputError.length > 0 ? STATUS_DANGER : LINE }) .defaultFocus(false) - .enabled(true) + .enabled(!this.isBusy) .onChange((value: string) => { this.inputText = value; this.inputError = ''; @@ -104,7 +126,7 @@ export struct ToolConfirmationPanel { } private approve(): void { - if (this.toolId.length === 0) { + if (this.toolId.length === 0 || this.isBusy) { return; } if (!this.hasEditableInput) { @@ -118,7 +140,7 @@ export struct ToolConfirmationPanel { } try { const parsed = JSON.parse(rawInput) as Object; - if (parsed === null || Array.isArray(parsed)) { + if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { this.inputError = RemoteI18n.t('chat.jsonObjectRequired'); return; } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolStatusList.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolStatusList.ets index 9a1fe3b0c9..45c0b49482 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolStatusList.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/ToolStatusList.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxState } from '../../model/InteractionMailbox'; import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { ConversationUiPlanTool, @@ -65,6 +66,7 @@ interface ToolRenderEntry { @ComponentV2 export struct ToolStatusList { + @Param interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); @Param tools: ConversationUiToolStatus[] = []; /** Reasoning that ran between these tools; folded away with them. */ @Param thinkingParts: ActivityThinkingPart[] = []; @@ -82,6 +84,7 @@ export struct ToolStatusList { @Event onBuildPlan: (path: string, name: string) => void = (_path: string, _name: string) => {}; @Local expanded: boolean = false; @Local expandedToolKey: string = ''; + @Local editingApprovalKey: string = ''; build() { Column({ space: 4 }) { @@ -245,16 +248,27 @@ export struct ToolStatusList { } } - if (this.isPendingConfirmation(tool)) { + if (this.isPendingConfirmation(tool) && !this.interactionMailbox.ownsToolInteraction(tool.id || '')) { + if (this.hasEditableToolInput(tool)) { + Row() { + Blank() + Button(RemoteI18n.t(this.editingApprovalKey === this.toolKey(tool, index) ? 'chat.hideApprovalInput' : 'chat.editApprovalInput')) + .fontSize(MobileDesignTypography.labelSmall.size).fontColor(MUTED) + .backgroundColor(SOFT).height(32).padding({ left: 8, right: 8 }) + .enabled(!this.isBusy) + .onClick(() => { this.editingApprovalKey = this.editingApprovalKey === this.toolKey(tool, index) ? '' : this.toolKey(tool, index); }) + }.width('100%') + } ToolConfirmationPanel({ toolId: tool.permission_request_id ? `permission:${tool.permission_request_id}` : tool.id || '', defaultInputText: this.defaultToolInputText(tool), - hasEditableInput: this.hasEditableToolInput(tool), + hasEditableInput: this.hasEditableToolInput(tool) && this.editingApprovalKey === this.toolKey(tool, index), + isBusy: this.isBusy, onApproveTool: this.onApproveTool, onRejectTool: this.onRejectTool }) } - if (this.isQuestionTool(tool)) { + if (this.isQuestionTool(tool) && !this.interactionMailbox.ownsToolInteraction(tool.id || '')) { ToolQuestionAnswerPanel({ toolId: tool.id || '', questionsJson: this.questionInputJson(tool), @@ -262,7 +276,8 @@ export struct ToolStatusList { onAnswerQuestion: this.onAnswerQuestion }) } - if (this.isRunningTool(tool) || this.isQuestionTool(tool)) { + if ((this.isRunningTool(tool) || this.isQuestionTool(tool)) && + !(this.isQuestionTool(tool) && this.interactionMailbox.ownsToolInteraction(tool.id || ''))) { Row() { Blank() Text(RemoteI18n.t('chat.cancelTool')) diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WideConversationHost.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WideConversationHost.ets index 325e89b138..40879b9ef6 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WideConversationHost.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WideConversationHost.ets @@ -178,6 +178,8 @@ export struct WideConversationHost { savedConnectionsTargetId: this.remotePageState.savedConnectionsTargetId, workspaceName: this.remotePageState.workspaceName, workspacePath: this.remotePageState.workspacePath, + workspaceConnectionId: this.remotePageState.workspaceConnectionId, + workspaceSshHost: this.remotePageState.workspaceSshHost, workspaceKind: this.remotePageState.workspaceKind, connectionState: this.remotePageState.connectionState, controlTargetType: this.remotePageState.controlTargetType, @@ -191,17 +193,18 @@ export struct WideConversationHost { REMOTE_CAPABILITY_HARNESS_PROFILES_V1), workspacePickerPlacement: this.sessionDetailsPlacement, onOpenSession: this.actions.onRemoteHome.openSessionInPlace, - onCreateInWorkspace: (deviceId: string, path: string, agentType: string, connectionId?: string) => { - this.actions.onRemoteHome.createInWorkspaceInPlace(path, agentType, deviceId, connectionId); + onCreateInWorkspace: (deviceId: string, path: string, agentType: string, connectionId?: string, sshHost?: string) => { + this.actions.onRemoteHome.createInWorkspaceInPlace(path, agentType, deviceId, connectionId, sshHost); }, - onOpenWorkspace: (deviceId: string, path: string, connectionId?: string) => { - this.actions.onRemoteHome.selectWorkspace(path, deviceId, connectionId); + onOpenWorkspace: (deviceId: string, path: string, connectionId?: string, sshHost?: string) => { + this.actions.onRemoteHome.selectWorkspace(path, deviceId, connectionId, sshHost); }, - onExpandWorkspace: async (deviceId: string, path: string): Promise => { - await this.actions.onRemoteHome.loadWorkspaceSessions(deviceId, path); + onExpandWorkspace: async (deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise => { + await this.actions.onRemoteHome.loadWorkspaceSessions(deviceId, path, remoteConnectionId, remoteSshHost); }, onSelectDevice: this.actions.onRemoteHome.selectDirectoryDevice, onRetryDevice: this.actions.onRemoteHome.retryDirectoryDevice, + onReconnect: this.actions.onRemoteHome.reconnect, onAddWorkspace: this.actions.onRemoteHome.addDirectoryWorkspace, onRefreshDevices: this.actions.onSidebar.refreshDevices, onAddDevice: this.actions.onSidebar.addDesktop diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceTerminalView.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceTerminalView.ets index 7b02ea9701..1b866a0ef5 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceTerminalView.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceTerminalView.ets @@ -65,6 +65,10 @@ export struct WorkspaceTerminalView { } catch (_error) { this.reset = true; this.dirty = true; } finally { this.sending = false; } } + onWillApplyTheme(_theme: Theme): void { + this.dirty = true; + void this.flush(); + } aboutToDisappear(): void { this.active = false; this.ready = false; } build() { Web({ src: $rawfile('terminal/index.html'), controller: this.controller }) diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets index 9db168947a..b822c40d21 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WorkspaceToolsPanel.ets @@ -1,7 +1,7 @@ import { RuntimeLocationPolicy } from '../policy/RuntimeLocationPolicy'; import { WorkspaceFileEditorView } from './WorkspaceFileEditorView'; import { WorkspaceTerminalView } from './WorkspaceTerminalView'; -import { WorkspaceToolsState, WorkspaceToolsIntent, WorkspaceFileEntry, WorkspaceTerminalEntry } from '../state/WorkspaceToolsState'; +import { WorkspaceToolsState, WorkspaceToolsIntent, WorkspaceFileEntry, WorkspaceTerminalEntry, WorkspaceToolLocation } from '../state/WorkspaceToolsState'; import { MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { RemoteI18n } from '../../i18n/RemoteI18n'; import { INK, MUTED, PAGE_BG, LINE } from './Theme'; @@ -10,16 +10,24 @@ import { INK, MUTED, PAGE_BG, LINE } from './Theme'; export struct WorkspaceToolsPanel { @Param state: WorkspaceToolsState = new WorkspaceToolsState(); @Event onIntent: (intent: WorkspaceToolsIntent) => void = (_intent: WorkspaceToolsIntent) => {}; - @Local path: string = ''; - @Local input: string = ''; - @Local confirmDelete: boolean = false; + private filesScroller: Scroller = new Scroller(); + @Monitor('state.directory', 'state.connectionId', 'state.sort') + private onFileLocationChanged(): void { + this.filesScroller.scrollEdge(Edge.Top); + } build() { Column({ space: 8 }) { Row() { Text(RemoteI18n.t('workspaceTools.title')).fontSize(MobileDesignTypography.bodyLarge.size).fontColor(INK).layoutWeight(1) - Button(RemoteI18n.t('common.close')).onClick(() => this.onIntent({ type: 'close' })) + Button(RemoteI18n.t('common.close')).fontColor(INK).backgroundColor(PAGE_BG).onClick(() => this.onIntent({ type: 'close' })) + } + if (this.state.deviceScope) { + Select([{ value: RemoteI18n.t('workspaceTools.runtimeLocal') }, ...this.state.locations.map((item: WorkspaceToolLocation): SelectOption => ({ value: item.name }))]) + .value(this.state.locations.find((item: WorkspaceToolLocation): boolean => item.id === this.state.connectionId)?.name || RemoteI18n.t('workspaceTools.runtimeLocal')) + .fontColor(INK).width('100%').enabled(!this.state.busy) + .onSelect((index: number) => this.onIntent({ type: 'open-device', connectionId: index === 0 ? '' : this.state.locations[index - 1]?.id, tab: this.state.initialTab })) } - if (this.state.error.length > 0) { Text(this.state.error).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size) } + if (this.state.error.length > 0) { Text(this.state.saveConflict ? RemoteI18n.t('workspaceTools.fileConflict') : this.state.error).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size) } if (this.state.confirmDiscard) { Text(RemoteI18n.t('workspaceTools.unsaved')).fontColor(INK) Row({ space: 8 }) { @@ -28,64 +36,103 @@ export struct WorkspaceToolsPanel { } } Tabs({ index: this.state.initialTab }) { - TabContent() { this.Files() }.tabBar(RemoteI18n.t('workspaceTools.files')) - TabContent() { this.Terminal() }.tabBar(RemoteI18n.t('workspaceTools.terminal')) - }.layoutWeight(1) + TabContent() { this.Files() }.tabBar(new SubTabBarStyle(RemoteI18n.t('workspaceTools.files')).labelStyle({ selectedColor: INK, unselectedColor: MUTED }).indicator({ color: INK })) + TabContent() { this.Terminal() }.tabBar(new SubTabBarStyle(RemoteI18n.t('workspaceTools.terminal')).labelStyle({ selectedColor: INK, unselectedColor: MUTED }).indicator({ color: INK })) + }.onChange((index: number) => this.onIntent({ type: 'panel', tab: index })).layoutWeight(1) }.padding(16).width('100%').height('100%').backgroundColor(PAGE_BG) } @Builder private Files() { if (this.state.editorVisible) { this.Editor() } else { this.FileDirectory() } } + private sortTitle(): string { + if (this.state.sort === 1) return RemoteI18n.t('workspaceTools.nameDesc'); + if (this.state.sort === 2) return RemoteI18n.t('workspaceTools.modifiedAsc'); + if (this.state.sort === 3) return RemoteI18n.t('workspaceTools.modifiedDesc'); + return RemoteI18n.t('workspaceTools.nameAsc'); + } + private fileActionTitle(): string { + if (this.state.fileAction === 'file') return RemoteI18n.t('workspaceTools.newFile'); + if (this.state.fileAction === 'folder') return RemoteI18n.t('workspaceTools.mkdir'); + if (this.state.fileAction === 'rename') return RemoteI18n.t('workspaceTools.rename'); + if (this.state.fileAction === 'upload') return RemoteI18n.t('workspaceTools.upload'); + return RemoteI18n.t('workspaceTools.confirmDelete'); + } + @Builder + private FileActionForm() { + Column({ space: 16 }) { + Row({ space: 12 }) { + Button(RemoteI18n.t('common.cancel')).fontColor(INK).backgroundColor(PAGE_BG).height(44) + .onClick(() => this.onIntent({ type: 'file-action-cancel' })) + Text(this.fileActionTitle()).fontColor(INK).fontSize(MobileDesignTypography.bodyLarge.size).layoutWeight(1).maxLines(2) + Button(this.fileActionTitle()).fontColor(PAGE_BG).backgroundColor(INK).height(44) + .enabled(this.state.fileAction === 'delete' || this.state.fileActionName.trim().length > 0) + .onClick(() => this.onIntent({ type: 'file-action-submit' })) + }.enabled(!this.state.busy).width('100%') + if (this.state.fileAction === 'delete') { Text(this.state.fileActionPath).fontColor(MUTED) } + else { + TextInput({ text: this.state.fileActionName, placeholder: RemoteI18n.t('workspaceTools.fileName') }).fontColor(INK).enabled(!this.state.busy) + .onChange((value: string) => this.onIntent({ type: 'file-action-name', text: value })) + .onSubmit(() => { if (!this.state.busy && this.state.fileActionName.trim()) this.onIntent({ type: 'file-action-submit' }); }) + } + if (this.state.error) { Text(this.state.error).fontColor(MUTED) } + if (this.state.busy) { LoadingProgress().width(24).height(24).color(MUTED) } + }.padding(24).width('100%') + } @Builder private FileDirectory() { Column({ space: 8 }) { - Text(this.state.directory).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size).maxLines(2) - Row({ space: 8 }) { - Button(RemoteI18n.t('workspaceTools.up')).onClick(() => { - const parent = RuntimeLocationPolicy.parent(this.state.directory); - this.onIntent({ type: 'directory', path: parent }); - }) - Button(RemoteI18n.t('workspaceTools.refresh')).onClick(() => this.onIntent({ type: 'directory' })) - }.enabled(!this.state.busy) - Select([ + Text(this.state.directory).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size).maxLines(2).width('100%').textAlign(TextAlign.Start) + Row({ space: 4 }) { + Button() { SymbolGlyph($r('sys.symbol.arrow_up')).fontColor([INK]).fontSize(20) } + .width(44).height(44).backgroundColor(PAGE_BG).accessibilityText(RemoteI18n.t('workspaceTools.up')) + .onClick(() => this.onIntent({ type: 'directory', path: RuntimeLocationPolicy.parent(this.state.directory) })) + Button() { SymbolGlyph($r('sys.symbol.arrow_clockwise')).fontColor([INK]).fontSize(20) } + .width(44).height(44).backgroundColor(PAGE_BG).accessibilityText(RemoteI18n.t('workspaceTools.refresh')) + .onClick(() => this.onIntent({ type: 'directory' })) + Select([ { value: RemoteI18n.t('workspaceTools.nameAsc') }, { value: RemoteI18n.t('workspaceTools.nameDesc') }, { value: RemoteI18n.t('workspaceTools.modifiedAsc') }, { value: RemoteI18n.t('workspaceTools.modifiedDesc') } - ]).selected(this.state.sort).onSelect((index: number) => this.onIntent({ type: 'sort', text: String(index) })) - List() { + ]).selected(this.state.sort).value(this.sortTitle()).fontColor(INK).layoutWeight(1).enabled(!this.state.busy) + .onSelect((index: number) => this.onIntent({ type: 'sort', text: String(index) })) + Button() { SymbolGlyph($r('sys.symbol.plus')).fontColor([INK]).fontSize(20) } + .width(44).height(44).backgroundColor(PAGE_BG).bindMenu([ + { value: RemoteI18n.t('workspaceTools.newFile'), action: () => this.onIntent({ type: 'file-action', text: 'file' }) }, + { value: RemoteI18n.t('workspaceTools.mkdir'), action: () => this.onIntent({ type: 'file-action', text: 'folder' }) }, + { value: RemoteI18n.t('workspaceTools.upload'), action: () => this.onIntent({ type: 'file-action', text: 'upload' }) } + ]) + }.enabled(!this.state.busy).width('100%') + if (this.state.busy) { LoadingProgress().width(24).height(24).color(MUTED) } + List({ scroller: this.filesScroller }) { ForEach(this.state.entries, (entry: WorkspaceFileEntry) => { ListItem() { - Text(`${entry.isDirectory ? 'โ–ธ ' : ''}${entry.name}`).fontColor(INK).fontSize(MobileDesignTypography.bodyMedium.size).padding(8) - .onClick(() => { this.path = entry.path; this.confirmDelete = false; this.onIntent({ type: entry.isDirectory ? 'directory' : 'read', path: entry.path }); }) + Row({ space: 8 }) { + Row({ space: 12 }) { + SymbolGlyph(entry.isDirectory ? $r('sys.symbol.folder') : $r('sys.symbol.doc')).fontColor([MUTED]).fontSize(22) + Text(entry.name).fontColor(INK).fontSize(MobileDesignTypography.bodyMedium.size).maxLines(2).layoutWeight(1) + }.layoutWeight(1).constraintSize({ minHeight: 52 }).onClick(() => this.onIntent({ type: entry.isDirectory ? 'directory' : 'read', path: entry.path })) + Button() { Text('โ‹ฏ').fontColor(MUTED).fontSize(MobileDesignTypography.headlineSmall.size) } + .width(44).height(44).backgroundColor(PAGE_BG).bindMenu([ + { value: RemoteI18n.t('workspaceTools.rename'), action: () => this.onIntent({ type: 'file-action', text: 'rename', path: entry.path }) }, + { value: RemoteI18n.t('workspaceTools.delete'), action: () => this.onIntent({ type: 'file-action', text: 'delete', path: entry.path }) }, + { value: RemoteI18n.t('workspaceTools.download'), enabled: !entry.isDirectory, action: () => this.onIntent({ type: 'download', path: entry.path }) } + ]) + }.width('100%') } }, (entry: WorkspaceFileEntry) => entry.path) - if (this.state.hasMore) { ListItem() { Button(RemoteI18n.t('workspaceTools.more')).onClick(() => this.onIntent({ type: 'more' })) } } - }.layoutWeight(1).enabled(!this.state.busy) - Divider().color(LINE) - TextInput({ text: this.path, placeholder: RemoteI18n.t('workspaceTools.path') }).onChange((value: string) => { this.path = value; this.confirmDelete = false; }) - Row({ space: 8 }) { - Button(RemoteI18n.t('workspaceTools.newFile')).onClick(() => this.onIntent({ type: 'read-new', path: this.path })) - Button(RemoteI18n.t('workspaceTools.mkdir')).onClick(() => this.onIntent({ type: 'mkdir', path: this.path })) - Button(RemoteI18n.t('workspaceTools.rename')).enabled(this.state.selectedFile.length > 0).onClick(() => this.onIntent({ type: 'rename', path: this.path })) - }.enabled(!this.state.busy && this.path.length > 0) - Row({ space: 8 }) { - Button(RemoteI18n.t('workspaceTools.download')).enabled(this.path.length > 0).onClick(() => this.onIntent({ type: 'download', path: this.path })) - Button(RemoteI18n.t('workspaceTools.upload')).enabled(this.path.length > 0 && !this.state.uploadPending).onClick(() => this.onIntent({ type: 'upload', path: this.path })) - if (this.state.uploadPending) { - Button(RemoteI18n.t('workspaceTools.resumeUpload')).onClick(() => this.onIntent({ type: 'upload-resume' })) - Button(RemoteI18n.t('workspaceTools.cancelUpload')).onClick(() => this.onIntent({ type: 'upload-cancel' })) - } - }.enabled(!this.state.busy) + if (this.state.hasMore) { ListItem() { Button(RemoteI18n.t('workspaceTools.more')).fontColor(INK).backgroundColor(PAGE_BG).onClick(() => this.onIntent({ type: 'more' })) } } + }.divider({ strokeWidth: 1, color: LINE }).layoutWeight(1).enabled(!this.state.busy) if (this.state.downloadStatus) { Text(this.state.downloadStatus).fontColor(MUTED).fontSize(MobileDesignTypography.bodySmall.size) } - if (this.state.uploadPending) { Progress({ value: this.state.uploadProgress, total: 100, type: ProgressType.Linear }) } - Row({ space: 8 }) { - Button(RemoteI18n.t(this.confirmDelete ? 'workspaceTools.confirmDelete' : 'workspaceTools.delete')) - .enabled(this.state.selectedFile.length > 0).onClick(() => { - if (this.confirmDelete) { this.onIntent({ type: 'delete' }); this.confirmDelete = false; } else this.confirmDelete = true; - }) - Button(RemoteI18n.t('workspaceTools.rmdir')).enabled(this.path.length > 0).onClick(() => this.onIntent({ type: 'rmdir', path: this.path })) - }.enabled(!this.state.busy) - }.padding({ top: 8 }).width('100%').height('100%') + if (this.state.uploadPending) { + Progress({ value: this.state.uploadProgress, total: 100, type: ProgressType.Linear }) + Row({ space: 8 }) { + Button(RemoteI18n.t('workspaceTools.resumeUpload')).fontColor(INK).backgroundColor(PAGE_BG).onClick(() => this.onIntent({ type: 'upload-resume' })) + Button(RemoteI18n.t('workspaceTools.cancelUpload')).fontColor(INK).backgroundColor(PAGE_BG).onClick(() => this.onIntent({ type: 'upload-cancel' })) + }.enabled(!this.state.busy) + } + }.padding({ top: 12 }).width('100%').height('100%') + .bindSheet(this.state.fileAction.length > 0, this.FileActionForm(), { height: SheetSize.FIT_CONTENT, showClose: false, backgroundColor: PAGE_BG, keyboardAvoidMode: SheetKeyboardAvoidMode.TRANSLATE_AND_RESIZE, + onWillDismiss: (action: DismissSheetAction): void => { if (!this.state.busy) { this.onIntent({ type: 'file-action-cancel' }); action.dismiss(); } } }) } @Builder private Editor() { @@ -114,21 +161,36 @@ export struct WorkspaceToolsPanel { @Builder private Terminal() { Column({ space: 8 }) { + if (this.state.terminalId.length === 0) { + Column() { + Button(RemoteI18n.t('workspaceTools.createTerminal')) + .fontColor(INK).backgroundColor(PAGE_BG) + .border({ width: 1, color: LINE }).borderRadius(10).height(44) + .enabled(!this.state.busy) + .onClick(() => this.onIntent({ type: 'terminal-create' })) + if (this.state.terminals.length > 0) { + Select(this.terminalOptions()).value(RemoteI18n.t('workspaceTools.selectTerminal')).fontColor(INK) + .onSelect((index: number) => this.onIntent({ type: 'terminal-attach', path: this.state.terminals[index]?.id })) + } + }.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) + } else { Row({ space: 8 }) { - Button(RemoteI18n.t('workspaceTools.createTerminal')).onClick(() => this.onIntent({ type: 'terminal-create' })) + Button(RemoteI18n.t('workspaceTools.createTerminal')).fontColor(INK).backgroundColor(PAGE_BG).border({ width: 1, color: LINE }).borderRadius(10).height(44).onClick(() => this.onIntent({ type: 'terminal-create' })) Select(this.terminalOptions()).onSelect((index: number) => this.onIntent({ type: 'terminal-attach', path: this.state.terminals[index]?.id })) }.enabled(!this.state.busy) - Text(this.state.terminalId).fontSize(MobileDesignTypography.bodySmall.size).fontColor(MUTED) WorkspaceTerminalView({ terminalId: this.state.terminalId, output: this.state.output, onIntent: this.onIntent }).layoutWeight(1) - Scroll() { - Row({ space: 8 }) { - Button('Esc').onClick(() => this.onIntent({ type: 'terminal-write', text: '\u001b' })) - Button('Tab').onClick(() => this.onIntent({ type: 'terminal-write', text: '\t' })) - Button('Enter').onClick(() => this.onIntent({ type: 'terminal-write', text: '\r' })) - Button('Ctrl-C').onClick(() => this.onIntent({ type: 'terminal-write', text: '\u0003' })) - Button(RemoteI18n.t('workspaceTools.closeTerminal')).onClick(() => this.onIntent({ type: 'terminal-close' })) - }.enabled(this.state.terminalId.length > 0) - }.width('100%').height(48).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off) + Row({ space: 8 }) { + Scroll() { + Row({ space: 8 }) { + Button('Esc').fontColor(INK).backgroundColor(PAGE_BG).border({ width: 1, color: LINE }).borderRadius(10).height(44).onClick(() => this.onIntent({ type: 'terminal-write', text: '\u001b' })) + Button('Tab').fontColor(INK).backgroundColor(PAGE_BG).border({ width: 1, color: LINE }).borderRadius(10).height(44).onClick(() => this.onIntent({ type: 'terminal-write', text: '\t' })) + Button('Enter').fontColor(INK).backgroundColor(PAGE_BG).border({ width: 1, color: LINE }).borderRadius(10).height(44).onClick(() => this.onIntent({ type: 'terminal-write', text: '\r' })) + Button('Ctrl-C').fontColor(INK).backgroundColor(PAGE_BG).border({ width: 1, color: LINE }).borderRadius(10).height(44).onClick(() => this.onIntent({ type: 'terminal-write', text: '\u0003' })) + } + }.layoutWeight(1).height(48).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off) + Button(RemoteI18n.t('workspaceTools.closeTerminal')).fontColor(INK).backgroundColor(PAGE_BG).border({ width: 1, color: LINE }).borderRadius(10).height(44).onClick(() => this.onIntent({ type: 'terminal-close' })) + }.width('100%').enabled(this.state.terminalId.length > 0) + } }.padding({ top: 8 }).width('100%').height('100%') } } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/remote/RemoteSurfaceHost.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/remote/RemoteSurfaceHost.ets index 770f94927e..612b4a5b6b 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/remote/RemoteSurfaceHost.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/remote/RemoteSurfaceHost.ets @@ -203,7 +203,7 @@ export struct RemoteSurfaceHost { @Builder private ConnectionEntry() { - if (this.isSyncing()) { + if (this.isInitialLoading()) { RemoteSessionLoadingView() } else { this.CompactEmptyState() @@ -234,6 +234,9 @@ export struct RemoteSurfaceHost { @Builder private CompactEmptyState() { RecentRemoteHome({ + sessions: this.remotePageState.recentHomeSessions(), + desktopName: this.desktopName(), + onOpen: (session: RemoteSession) => this.openSession(session), connected: this.remotePageState.connectionState === 'connected', busy: this.isInitialLoading(), onBrowse: () => { if (this.compact) this.onOpenSidebar(); else this.onRestoreSidebar(); }, @@ -251,7 +254,7 @@ export struct RemoteSurfaceHost { Blank().width(48).height(48) } Column({ space: 4 }) { - Text(RemoteI18n.t('remote.chats')).fontSize(MobileDesignTypography.headlineMedium.size).fontWeight(FontWeight.Bold).fontColor(INK) + Text(RemoteI18n.t('app.title')).fontSize(MobileDesignTypography.headlineMedium.size).fontWeight(FontWeight.Bold).fontColor(INK) Text(this.desktopName()).fontSize(MobileDesignTypography.bodySmall.size).fontColor(MUTED).maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } @@ -354,10 +357,11 @@ export struct RemoteSurfaceHost { * placeholder rows would be a step backwards. */ private isInitialLoading(): boolean { - if (this.remotePageState.sessions.length > 0) { - return false; - } - return this.isSyncing(); + return RemoteCompactHomePolicy.shouldShowInitialLoading( + this.remotePageState.connectionState, + this.remotePageState.sessions.length, + this.remotePageState.isLoadingHome + ); } /** Work in progress, whether or not there is already a list behind it. */ @@ -388,8 +392,8 @@ export struct RemoteSurfaceHost { /** * Compact Remote Home names the bound desktop under the title, the same - * context the conversation header carries. Stays empty while disconnected so - * the connect state does not advertise a stale desktop. + * context the conversation header carries. Keep it stable during recovery so + * the content does not shift when the sidebar health state changes. */ private compactHeaderContext(): string { return RemoteCompactHomePolicy.headerSubtitle( diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/policy/RemoteCompactHomePolicy.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/policy/RemoteCompactHomePolicy.ets index d93f441f4a..ec3a51ffce 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/policy/RemoteCompactHomePolicy.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/policy/RemoteCompactHomePolicy.ets @@ -6,6 +6,16 @@ * cache as a live link: "pick a session" only belongs on a connected desktop. */ export class RemoteCompactHomePolicy { + static shouldShowInitialLoading(connectionState: string, sessionCount: number, loadingHome: boolean): boolean { + // Recovery keeps the existing surface, including an already empty home. + if (sessionCount > 0 || connectionState === 'reconnecting') return false; + return loadingHome || connectionState === 'parsing' || connectionState === 'pairing'; + } + + static shouldShowRecentSessions(connected: boolean, sessionCount: number): boolean { + return connected || sessionCount > 0; + } + static shouldShowWelcomeHome(controlTargetType: string, connectionState: string, signedIn: boolean = false): boolean { return !signedIn && controlTargetType === 'none' && connectionState !== 'connected'; } @@ -15,7 +25,8 @@ export class RemoteCompactHomePolicy { } static headerSubtitle(connectionState: string, desktopName: string): string { - if (RemoteCompactHomePolicy.shouldShowConnectHome(connectionState)) { + // Recovery retains the selected device context; health belongs in the sidebar. + if (connectionState !== 'connected' && connectionState !== 'reconnecting') { return ''; } return desktopName; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/InteractionMailboxPreview.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/InteractionMailboxPreview.ets new file mode 100644 index 0000000000..5583f7f921 --- /dev/null +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/InteractionMailboxPreview.ets @@ -0,0 +1,36 @@ +import { InteractionMailboxAction, InteractionMailboxActionType, InteractionMailboxState } from '../../model/InteractionMailbox'; +import { ConversationView } from '../components/ConversationView'; +import { ConversationViewState } from '../state/ConversationViewState'; +import { ChatSurface } from '../state/ChatSurface'; +import { ConversationIntentType, ConversationIntent } from '../actions/ConversationIntent'; + +/** Deterministic native UI fixture; never opens a transport or modifies account state. */ +@ComponentV2 +export struct InteractionMailboxPreview { + @Local state: ConversationViewState = this.initialState(); + + private initialState(): ConversationViewState { + const state = new ConversationViewState(); + state.surface = ChatSurface.Remote; + state.connectionState = 'connected'; + state.activeSession = { sessionId: 'mailbox-preview', title: 'Interaction mailbox', workspacePath: '/workspace', agentType: 'code' }; + state.interactionMailbox.sessionId = 'mailbox-preview'; + state.interactionMailbox.requests = [{ requestId: 'preview-write', sessionId: 'mailbox-preview', + action: 'write', resources: ['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/workspace/readme.md'], source: { identity: 'Write' } }]; + return state; + } + + build() { + ConversationView({ state: this.state, onIntent: (intent: ConversationIntent): void => { + if (intent.type === ConversationIntentType.Mailbox && intent.mailboxAction) this.handle(intent.mailboxAction); + } }) + } + + private handle(action: InteractionMailboxAction): void { + const next = this.initialState(); + if (action.type === InteractionMailboxActionType.Approve || action.type === InteractionMailboxActionType.Reject) { + next.interactionMailbox.failure = 'reply'; + } + this.state = next; + } +} diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/MobileDesignGallery.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/MobileDesignGallery.ets index 94ef825b27..0cf28e72f2 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/MobileDesignGallery.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/preview/MobileDesignGallery.ets @@ -1,4 +1,5 @@ import { WelcomeHome } from '../components/WelcomeHome'; +import { InteractionMailboxPreview } from './InteractionMailboxPreview'; import { MobileDesignGeometry, MobileDesignTypography } from '../../generated/MobileDesignTokens'; import { MobilePreviewMessage, @@ -33,6 +34,8 @@ struct MobileDesignGallery { build() { if (AppStorage.get('scenarioId') === 'welcome-home') { WelcomeHome() + } else if ((AppStorage.get('scenarioId') || '').startsWith('interaction-mailbox')) { + InteractionMailboxPreview() } else { this.ConversationPreview() } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntime.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntime.ets index 0e19aa4201..17304f914c 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntime.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntime.ets @@ -4,8 +4,8 @@ import { ConnectionErrorPolicy } from '../../services/ConnectionErrorPolicy'; import { CloudAccountDevice } from '../../services/CloudAccountClient'; import { RemoteLogger } from '../../services/RemoteLogger'; import { - stampRemoteSessionsDevice, - stampRemoteSessionsWorkspace + remoteWorkspaceKey, + stampRemoteSessionsDevice } from '../../services/RemoteSessionIdentity'; import { RemoteUiState } from '../../services/RemoteUiState'; import { AppRootHostPort } from '../host/AppRootHostAdapter'; @@ -240,14 +240,14 @@ export class AppRootRuntime extends AppRootRuntimeComposition { this.remoteActivityViewModel.stopHeartbeat(); } - async selectWorkspace(path: string, connectionId?: string): Promise { + async selectWorkspace(path: string, connectionId?: string, sshHost?: string): Promise { this.filePreviewController.close(); - await this.remoteWorkspaceViewModel.selectWorkspace(path, connectionId); + await this.remoteWorkspaceViewModel.selectWorkspace(path, connectionId, sshHost); } - async selectWorkspaceOnDevice(path: string, deviceId?: string, connectionId?: string): Promise { + async selectWorkspaceOnDevice(path: string, deviceId?: string, connectionId?: string, sshHost?: string): Promise { await this.conversationController.ensureRemoteControlTarget(deviceId); - await this.selectWorkspace(path, connectionId); + await this.selectWorkspace(path, connectionId, sshHost); } async selectDirectoryDevice(deviceId: string): Promise { @@ -307,18 +307,22 @@ export class AppRootRuntime extends AppRootRuntimeComposition { await this.deviceDirectoryViewModel.retryDevice(deviceId); } - async loadWorkspaceSessionsOnDevice(deviceId: string, path: string): Promise { + async loadWorkspaceSessionsOnDevice(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise { const targetDeviceId = deviceId.trim(); const targetPath = path.trim(); if (targetDeviceId.length === 0 || targetPath.length === 0) { return; } - await this.deviceDirectoryViewModel.loadWorkspaceSessions(targetDeviceId, targetPath); + await this.deviceDirectoryViewModel.loadWorkspaceSessions(targetDeviceId, targetPath, remoteConnectionId, remoteSshHost); } - publishWorkspaceSessionPage(path: string, result: SessionListResult): void { - this.remoteWorkspaceSessions = this.replaceWorkspaceSessions(path, result.sessions); - if (path === this.remotePageState.workspacePath) { + publishWorkspaceSessionPage(path: string, result: SessionListResult, remoteConnectionId?: string, remoteSshHost?: string): void { + this.remoteWorkspaceSessions = this.replaceWorkspaceSessions(path, result.sessions, remoteConnectionId, remoteSshHost); + if (remoteWorkspaceKey({ path, remoteConnectionId, remoteSshHost }) === remoteWorkspaceKey({ + path: this.remotePageState.workspacePath, + remoteConnectionId: this.remotePageState.workspaceConnectionId, + remoteSshHost: this.remotePageState.workspaceSshHost + })) { // Update the controller as well as the sidebar so a later title change // cannot republish the old current-workspace list. this.remoteSessionController.setSessions(result.sessions, result.hasMore); @@ -457,7 +461,11 @@ export class AppRootRuntime extends AppRootRuntimeComposition { */ async reconcileCreatedRemoteSession(session: SessionSummary): Promise { const workspacePath = session.workspacePath.trim(); - if (workspacePath.length === 0 || workspacePath === this.remotePageState.workspacePath) { + const remoteConnectionId = session.remoteConnectionId; + const remoteSshHost = session.remoteSshHost; + if (workspacePath.length === 0 || remoteWorkspaceKey({ path: workspacePath, remoteConnectionId, remoteSshHost }) === + remoteWorkspaceKey({ path: this.remotePageState.workspacePath, + remoteConnectionId: this.remotePageState.workspaceConnectionId, remoteSshHost: this.remotePageState.workspaceSshHost })) { await this.remoteSessionViewModel.refreshSessions(); return; } @@ -471,20 +479,23 @@ export class AppRootRuntime extends AppRootRuntimeComposition { updatedAt: now, createdAt: now, messageCount: session.initialTurnId ? 1 : 0, - workspacePath + workspacePath, + workspaceIdentity: { path: workspacePath, remoteConnectionId, remoteSshHost } }; this.remoteWorkspaceSessions = this.replaceWorkspaceSessions( workspacePath, - this.mergeSessions([created], this.sessionsForWorkspace(workspacePath)) + this.mergeSessions([created], this.sessionsForWorkspace(workspacePath, remoteConnectionId, remoteSshHost)), + remoteConnectionId, remoteSshHost ); this.publishRemoteWorkspaceSessions(); try { - const refreshed = await this.sessionManager.listSessionsForWorkspace(workspacePath, 50); + const refreshed = await this.sessionManager.listSessionsForWorkspace(workspacePath, 50, remoteConnectionId, remoteSshHost); const containsCreated = refreshed.some((item: RemoteSession): boolean => item.id === session.sessionId); this.remoteWorkspaceSessions = this.replaceWorkspaceSessions( workspacePath, - containsCreated ? refreshed : this.mergeSessions([created], refreshed) + containsCreated ? refreshed : this.mergeSessions([created], refreshed), + remoteConnectionId, remoteSshHost ); this.publishRemoteWorkspaceSessions(); } catch (err) { @@ -494,24 +505,31 @@ export class AppRootRuntime extends AppRootRuntimeComposition { } } - private sessionsForWorkspace(path: string): RemoteSession[] { - return this.remotePageState.sessions.filter((item: RemoteSession): boolean => item.workspacePath === path); + private sessionsForWorkspace(path: string, remoteConnectionId?: string, remoteSshHost?: string): RemoteSession[] { + const key = remoteWorkspaceKey({ path, remoteConnectionId, remoteSshHost }); + return this.remotePageState.sessions.filter((item: RemoteSession): boolean => + item.workspaceIdentity !== undefined && remoteWorkspaceKey(item.workspaceIdentity) === key); } - private replaceWorkspaceSessions(path: string, replacement: RemoteSession[]): RemoteSession[] { + private replaceWorkspaceSessions(path: string, replacement: RemoteSession[], remoteConnectionId?: string, remoteSshHost?: string): RemoteSession[] { + const key = remoteWorkspaceKey({ path, remoteConnectionId, remoteSshHost }); const preserved = this.remotePageState.sessions.filter((item: RemoteSession): boolean => { - return item.workspacePath !== path; + return !item.workspaceIdentity || remoteWorkspaceKey(item.workspaceIdentity) !== key; }); return replacement.concat(preserved); } private publishRemoteWorkspaceSessions(): void { - const currentPath = this.remotePageState.workspacePath; + const currentKey = remoteWorkspaceKey({ + path: this.remotePageState.workspacePath, + remoteConnectionId: this.remotePageState.workspaceConnectionId, + remoteSshHost: this.remotePageState.workspaceSshHost + }); const current = this.remotePageState.sessions.filter((item: RemoteSession): boolean => { - return (item.workspacePath || currentPath) === currentPath; + return !item.workspaceIdentity || remoteWorkspaceKey(item.workspaceIdentity) === currentKey; }); const extras = this.remoteWorkspaceSessions.filter((item: RemoteSession): boolean => { - return item.workspacePath !== currentPath; + return !item.workspaceIdentity || remoteWorkspaceKey(item.workspaceIdentity) !== currentKey; }); this.publishRemoteSessions(this.mergeSessions(current, extras), this.remotePageState.hasMoreSessions); } @@ -526,16 +544,19 @@ export class AppRootRuntime extends AppRootRuntimeComposition { */ publishRemoteSessions(sessions: RemoteSession[], hasMore: boolean): void { const deviceId = this.remotePageState.controlTargetDeviceId || this.remotePageState.desktopId; - const scoped = stampRemoteSessionsWorkspace(sessions, this.remotePageState.workspacePath); + const scoped = sessions; const stamped = stampRemoteSessionsDevice(scoped, deviceId); this.remotePageState.setSessions(stamped, hasMore); this.remoteSessionListCache.save(stamped, hasMore); const currentPath = this.remotePageState.workspacePath; + const remoteConnectionId = this.remotePageState.workspaceConnectionId; + const remoteSshHost = this.remotePageState.workspaceSshHost; + const currentKey = remoteWorkspaceKey({ path: currentPath, remoteConnectionId, remoteSshHost }); if (currentPath.length > 0) { const currentSessions = stamped.filter((item: RemoteSession): boolean => { - return (item.workspacePath || '') === currentPath; + return item.workspaceIdentity !== undefined && remoteWorkspaceKey(item.workspaceIdentity) === currentKey; }); - this.deviceDirectoryViewModel.captureWorkspaceSessions(deviceId, currentPath, currentSessions); + this.deviceDirectoryViewModel.captureWorkspaceSessions(deviceId, currentPath, currentSessions, remoteConnectionId, remoteSshHost); } this.deviceDirectoryViewModel.captureLive( deviceId, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntimeComposition.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntimeComposition.ets index 118b69eda2..fcdd8a3bb6 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntimeComposition.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/runtime/AppRootRuntimeComposition.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxAction, InteractionMailboxState } from '../../model/InteractionMailbox'; import { HostCatalogObserver } from '../../services/HostCatalogObserver'; import { WorkspaceToolsViewModel } from '../viewmodel/WorkspaceToolsViewModel'; import { WorkspaceToolsIntent } from '../state/WorkspaceToolsState'; @@ -124,22 +125,22 @@ export abstract class AppRootRuntimeComposition { abstract hasRemoteBindingForResume(): boolean; abstract isRemoteConversationContext(sessionId: string): boolean; abstract mergeSessions(primary: RemoteSession[], extras: RemoteSession[]): RemoteSession[]; - abstract loadWorkspaceSessionsOnDevice(deviceId: string, path: string): Promise; + abstract loadWorkspaceSessionsOnDevice(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise; abstract openAddConnectionFromSettings(): void; abstract openAppSidebar(): void; abstract openRemoteControlSettings(): void; abstract openWorkspacePickerOnDevice(deviceId: string): Promise; abstract pickImages(): Promise; abstract publishRemoteSessions(sessions: RemoteSession[], hasMore: boolean): void; - abstract publishWorkspaceSessionPage(path: string, result: SessionListResult): void; + abstract publishWorkspaceSessionPage(path: string, result: SessionListResult, remoteConnectionId?: string, remoteSshHost?: string): void; abstract reconcileCreatedRemoteSession(session: SessionSummary): Promise; abstract reconnect(): Promise; abstract reconnectActiveRemote(): Promise; abstract retryDirectoryDevice(deviceId: string): Promise; abstract selectDirectoryDevice(deviceId: string): Promise; abstract selectAssistant(path: string): Promise; - abstract selectWorkspace(path: string, connectionId?: string): Promise; - abstract selectWorkspaceOnDevice(path: string, deviceId?: string, connectionId?: string): Promise; + abstract selectWorkspace(path: string, connectionId?: string, sshHost?: string): Promise; + abstract selectWorkspaceOnDevice(path: string, deviceId?: string, connectionId?: string, sshHost?: string): Promise; abstract showVoiceInputError(message: string): void; abstract stopVoiceInput(showStatus: boolean): Promise; abstract toggleVoiceInput(): Promise; @@ -234,7 +235,7 @@ export abstract class AppRootRuntimeComposition { private readonly hostCatalogObserver: HostCatalogObserver = new HostCatalogObserver( this.sessionManager, async (): Promise => { - await Promise.all([this.remoteWorkspaceViewModel.loadRecentWorkspacesInBackground(), this.remoteSessionViewModel.refreshSessions()]); + await Promise.all([this.remoteWorkspaceViewModel.loadRecentWorkspacesInBackground(), this.remoteSessionViewModel.refreshSessions(), this.remoteChatPollingLifecycleController.refreshModelCatalog()]); }, (error: Error): void => { this.remotePageState.setError(ConnectionErrorPolicy.errorText(error)); } ); @@ -290,8 +291,8 @@ export abstract class AppRootRuntimeComposition { onCatalogLoading: (targetId: string): void => { this.deviceDirectoryViewModel.beginLiveCatalog(targetId); }, - onCatalogLoaded: (targetId: string, workspaces: RecentWorkspaceEntry[]): void => { - this.deviceDirectoryViewModel.completeLiveCatalog(targetId, workspaces); + onCatalogLoaded: (targetId: string, workspaces: RecentWorkspaceEntry[], source?: string, recentWorkspaces?: RecentWorkspaceEntry[]): void => { + this.deviceDirectoryViewModel.completeLiveCatalog(targetId, workspaces, source, recentWorkspaces); }, onCatalogFailed: (targetId: string): void => { this.deviceDirectoryViewModel.failLiveCatalog(targetId); @@ -521,9 +522,15 @@ export abstract class AppRootRuntimeComposition { this.isRemoteConversationContext(sessionId) && this.remoteConnectionController.ensureAvailable(); }, + onMailbox: (state: InteractionMailboxState): void => { + if (!state.sessionId || this.remotePageState.activeSession.sessionId === state.sessionId) { + this.remotePageState.interactionMailbox = state; + } + }, onSnapshot: (snapshot: RemoteChatPollingSnapshot) => { this.remotePageState.setConversationLoading(false); if (snapshot.hasMoreMessages !== undefined) this.remotePageState.setHasMoreMessages(snapshot.hasMoreMessages); + this.remotePageState.setHistoryLoadState(snapshot.historyLoading === true, snapshot.historyFailed === true); this.conversationController.applyRemoteSnapshot(snapshot); if (snapshot.activeTurn && snapshot.activeTurn.id.length > 0) { const status = (snapshot.activeTurn.status || '').toLowerCase(); @@ -703,10 +710,10 @@ export abstract class AppRootRuntimeComposition { }, activeDeviceConnected: (): boolean => (this.remotePageState.connectionState as ConnectionState) === ConnectionState.Connected, - loadActiveWorkspaceSessions: (path: string): Promise => - this.sessionManager.listSessionPageForWorkspace(path, 50), - onActiveWorkspaceSessions: (path: string, result: SessionListResult): void => - this.publishWorkspaceSessionPage(path, result) + loadActiveWorkspaceSessions: (path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise => + this.sessionManager.listSessionPageForWorkspace(path, 50, remoteConnectionId, remoteSshHost), + onActiveWorkspaceSessions: (path: string, result: SessionListResult, remoteConnectionId?: string, remoteSshHost?: string): void => + this.publishWorkspaceSessionPage(path, result, remoteConnectionId, remoteSshHost) }, this.remoteSessionListStore ); @@ -745,6 +752,7 @@ export abstract class AppRootRuntimeComposition { ); readonly conversationIntentDispatcher: ConversationIntentDispatcher = new ConversationIntentDispatcher({ + mailbox: async (action: InteractionMailboxAction): Promise => { await this.remoteChatPollingLifecycleController.dispatchMailbox(action); }, openSidebar: (): void => this.openAppSidebar(), back: (): void => this.exitActiveChat(), newRemoteSession: (): void => { this.conversationController.createRemoteSession('code'); }, @@ -797,8 +805,8 @@ export abstract class AppRootRuntimeComposition { refresh: (): void => { this.remoteSessionViewModel.refreshSessions(); }, showWorkspaces: (): void => { this.remoteWorkspaceViewModel.toggleRecentWorkspaces(); }, showAssistants: (): void => { this.remoteWorkspaceViewModel.toggleAssistants(); }, - selectWorkspace: (path: string, deviceId?: string, connectionId?: string): void => { - void this.selectWorkspaceOnDevice(path, deviceId, connectionId); + selectWorkspace: (path: string, deviceId?: string, connectionId?: string, sshHost?: string): void => { + void this.selectWorkspaceOnDevice(path, deviceId, connectionId, sshHost); }, selectAssistant: (path: string): void => { this.selectAssistant(path); }, cancelWorkspace: (): void => this.remotePageState.setWorkspacePickerVisible(false), @@ -813,11 +821,11 @@ export abstract class AppRootRuntimeComposition { createInPlace: (agentType: string): void => { this.conversationController.createRemoteSession(agentType, true); }, - createInWorkspace: (path: string, agentType: string, deviceId?: string, connectionId?: string): void => { - this.conversationController.createRemoteSessionInWorkspace(path, agentType, false, deviceId, connectionId); + createInWorkspace: (path: string, agentType: string, deviceId?: string, connectionId?: string, sshHost?: string): void => { + this.conversationController.createRemoteSessionInWorkspace(path, agentType, false, deviceId, connectionId, sshHost); }, - createInWorkspaceInPlace: (path: string, agentType: string, deviceId?: string, connectionId?: string): void => { - this.conversationController.createRemoteSessionInWorkspace(path, agentType, true, deviceId, connectionId); + createInWorkspaceInPlace: (path: string, agentType: string, deviceId?: string, connectionId?: string, sshHost?: string): void => { + this.conversationController.createRemoteSessionInWorkspace(path, agentType, true, deviceId, connectionId, sshHost); }, selectDirectoryDevice: (deviceId: string): void => { void this.selectDirectoryDevice(deviceId); @@ -828,8 +836,8 @@ export abstract class AppRootRuntimeComposition { retryDirectoryDevice: (deviceId: string): void => { void this.retryDirectoryDevice(deviceId); }, - loadWorkspaceSessions: async (deviceId: string, path: string): Promise => { - await this.loadWorkspaceSessionsOnDevice(deviceId, path); + loadWorkspaceSessions: async (deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise => { + await this.loadWorkspaceSessionsOnDevice(deviceId, path, remoteConnectionId, remoteSshHost); }, openSession: (session: RemoteSession): void => this.conversationController.openHomeSession(session), openSessionInPlace: (session: RemoteSession): void => this.conversationController.openHomeSession(session, true), @@ -849,8 +857,9 @@ export abstract class AppRootRuntimeComposition { this.openConnectSheet(CONNECT_INTENT_SCAN); }, refreshDevices: async (): Promise => { - await this.settingsController.refreshAccountDevicesIfStale(0); - await this.deviceDirectoryViewModel.refreshExpandedWorkspaceSessions(); + await this.deviceDirectoryViewModel.refreshDevices(async (): Promise => { + await this.settingsController.listCloudAccountDevices(); + }); }, refreshSessions: (): Promise => this.deviceDirectoryViewModel.refreshExpandedWorkspaceSessions(), settings: (): void => { this.closeAppSidebar(); this.appShellState.openSettings('general'); }, @@ -997,7 +1006,9 @@ export abstract class AppRootRuntimeComposition { this.remotePageState.workspacePath, this.remotePageState.assistantId, this.remotePageState.workspaceBranch, - this.remotePageState.workspaceKind + this.remotePageState.workspaceKind, + this.remotePageState.workspaceConnectionId, + this.remotePageState.workspaceSshHost ); this.remotePageState.setDownloadStatus( this.remotePageState.downloadingFilePath, diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationCoreState.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationCoreState.ets index cf0054bdab..1fbd378131 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationCoreState.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationCoreState.ets @@ -23,6 +23,8 @@ export class ConversationCoreState { @Trace optimisticMessages: ChatMessage[] = []; @Trace activeTurnMessage: ChatMessage = RemoteUiState.emptyActiveTurn(); @Trace hasMoreMessages: boolean = false; + @Trace historyLoading: boolean = false; + @Trace historyFailed: boolean = false; @Trace timelineItems: ObservableChatTimelineItem[] = []; @Trace timelineRevision: number = 0; @Trace isBusy: boolean = false; @@ -87,6 +89,8 @@ export class ConversationCoreState { this.optimisticMessages = []; this.activeTurnMessage = RemoteUiState.emptyActiveTurn(); this.hasMoreMessages = false; + this.historyLoading = false; + this.historyFailed = false; this.timelineItems = []; this.timelineRowStore.clear(); this.timelineRevision = this.timelineRevisionTracker.reset(); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationViewState.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationViewState.ets index d04ba9413a..810250efca 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationViewState.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/ConversationViewState.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxState } from '../../model/InteractionMailbox'; import { ObservableChatTimelineItem } from '../../model/ChatTimelineModels'; import { GeneralChatServiceStatus @@ -24,6 +25,7 @@ import { RemotePageState } from './RemotePageState'; /** Immutable presentation state consumed by ConversationView. */ export class ConversationViewState { + interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); activeSession: ConversationUiSession = ConversationViewState.emptySession(); surface: ChatSurface = ChatSurface.General; desktopName: string = ''; @@ -36,6 +38,8 @@ export class ConversationViewState { isLoadingConversation: boolean = false; canStop: boolean = false; hasMoreMessages: boolean = false; + historyLoading: boolean = false; + historyFailed: boolean = false; timelineItems: ObservableChatTimelineItem[] = []; timelineRevision: number = 0; showSuggestionsWhenEmpty: boolean = false; @@ -76,6 +80,7 @@ export class ConversationViewState { state.downloadingFilePath = remote.downloadingFilePath; state.downloadedFilePath = remote.downloadedFilePath; state.fileDownloadStatus = remote.fileDownloadStatus; + state.interactionMailbox = remote.interactionMailbox; return state; } @@ -95,6 +100,8 @@ export class ConversationViewState { state.isBusy = core.isBusy; state.canStop = core.hasRunningActiveTurn(); state.hasMoreMessages = core.hasMoreMessages; + state.historyLoading = core.historyLoading; + state.historyFailed = core.historyFailed; state.timelineItems = core.timelineItems; state.timelineRevision = core.timelineRevision; state.modelCatalog = toConversationUiModelCatalog(core.modelCatalog); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/DeviceDirectoryState.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/DeviceDirectoryState.ets index 123371c6bb..85da1fb391 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/DeviceDirectoryState.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/DeviceDirectoryState.ets @@ -8,6 +8,8 @@ export type WorkspaceDirectoryStatus = 'idle' | 'loading' | 'ready' | 'failed'; export class WorkspaceDirectoryState { @Trace deviceId: string = ''; @Trace path: string = ''; + @Trace remoteConnectionId: string = ''; + @Trace remoteSshHost: string = ''; @Trace expanded: boolean = false; @Trace status: WorkspaceDirectoryStatus = 'idle'; @Trace hasLoadedSessions: boolean = false; @@ -27,12 +29,17 @@ export class DeviceDirectoryEntry { @Trace status: DeviceDirectoryStatus = 'idle'; @Trace errorText: string = ''; @Trace workspaces: RecentWorkspaceEntry[] = []; + @Trace catalogSource: string = ''; + @Trace recentWorkspaces: RecentWorkspaceEntry[] = []; @Trace sessions: RemoteSession[] = []; } /** Snapshot of every account desktop the sidebar can observe. */ @ObservedV2 export class DeviceDirectoryState { + @Trace refreshing: boolean = false; + @Trace refreshError: string = ''; + refreshGeneration: number = 0; @Trace devices: DeviceDirectoryEntry[] = []; /** Disclosure state is independent of device catalog projection instances. */ @Trace workspaceDirectory: WorkspaceDirectoryState[] = []; @@ -44,6 +51,7 @@ export class DeviceDirectoryState { } clear(): void { + this.refreshGeneration++; this.refreshing = false; this.refreshError = ''; this.devices = []; this.workspaceDirectory = []; this.selectedDeviceId = ''; @@ -66,33 +74,33 @@ export class DeviceDirectoryState { return this.devices.find((item: DeviceDirectoryEntry): boolean => item.deviceId === deviceId); } - workspaceExpanded(deviceId: string, path: string): boolean { - return this.findWorkspace(deviceId, path)?.expanded || false; + workspaceExpanded(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): boolean { + return this.findWorkspace(deviceId, path, remoteConnectionId, remoteSshHost)?.expanded || false; } - workspaceStatus(deviceId: string, path: string): WorkspaceDirectoryStatus { - return this.findWorkspace(deviceId, path)?.status || 'idle'; + workspaceStatus(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): WorkspaceDirectoryStatus { + return this.findWorkspace(deviceId, path, remoteConnectionId, remoteSshHost)?.status || 'idle'; } - workspaceHasLoadedSessions(deviceId: string, path: string): boolean { - return this.findWorkspace(deviceId, path)?.hasLoadedSessions || false; + workspaceHasLoadedSessions(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): boolean { + return this.findWorkspace(deviceId, path, remoteConnectionId, remoteSshHost)?.hasLoadedSessions || false; } /** A request retains this object so a reset cannot publish into its successor. */ - workspaceLoadState(deviceId: string, path: string): WorkspaceDirectoryState { - return this.ensureWorkspace(deviceId, path); + workspaceLoadState(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): WorkspaceDirectoryState { + return this.ensureWorkspace(deviceId, path, remoteConnectionId, remoteSshHost); } isCurrentWorkspaceLoad(state: WorkspaceDirectoryState): boolean { - return this.findWorkspace(state.deviceId, state.path) === state; + return this.findWorkspace(state.deviceId, state.path, state.remoteConnectionId, state.remoteSshHost) === state; } - setWorkspaceExpanded(deviceId: string, path: string, expanded: boolean): void { - this.ensureWorkspace(deviceId, path).expanded = expanded; + setWorkspaceExpanded(deviceId: string, path: string, expanded: boolean, remoteConnectionId?: string, remoteSshHost?: string): void { + this.ensureWorkspace(deviceId, path, remoteConnectionId, remoteSshHost).expanded = expanded; } - setWorkspaceStatus(deviceId: string, path: string, status: WorkspaceDirectoryStatus): void { - this.ensureWorkspace(deviceId, path).status = status; + setWorkspaceStatus(deviceId: string, path: string, status: WorkspaceDirectoryStatus, remoteConnectionId?: string, remoteSshHost?: string): void { + this.ensureWorkspace(deviceId, path, remoteConnectionId, remoteSshHost).status = status; } /** @@ -113,24 +121,27 @@ export class DeviceDirectoryState { ); } - private findWorkspace(deviceId: string, path: string): WorkspaceDirectoryState | undefined { + private findWorkspace(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): WorkspaceDirectoryState | undefined { const targetDeviceId = deviceId.trim(); const normalizedPath = DeviceDirectoryState.normalizeWorkspacePath(path); return this.workspaceDirectory.find((item: WorkspaceDirectoryState): boolean => - item.deviceId === targetDeviceId && item.path === normalizedPath + item.deviceId === targetDeviceId && item.path === normalizedPath && + item.remoteConnectionId === (remoteConnectionId?.trim() || '') && item.remoteSshHost === (remoteSshHost?.trim() || '') ); } - private ensureWorkspace(deviceId: string, path: string): WorkspaceDirectoryState { + private ensureWorkspace(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): WorkspaceDirectoryState { const targetDeviceId = deviceId.trim(); const normalizedPath = DeviceDirectoryState.normalizeWorkspacePath(path); - const existing = this.findWorkspace(targetDeviceId, normalizedPath); + const existing = this.findWorkspace(targetDeviceId, normalizedPath, remoteConnectionId, remoteSshHost); if (existing) { return existing; } const created = new WorkspaceDirectoryState(); created.deviceId = targetDeviceId; created.path = normalizedPath; + created.remoteConnectionId = remoteConnectionId?.trim() || ''; + created.remoteSshHost = remoteSshHost?.trim() || ''; this.workspaceDirectory = this.workspaceDirectory.concat([created]); return created; } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/RemotePageState.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/RemotePageState.ets index 3956bf30a3..cbfb88b9c2 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/RemotePageState.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/RemotePageState.ets @@ -1,3 +1,5 @@ +import { TimeFormat } from '../../services/TimeFormat'; +import { InteractionMailboxState } from '../../model/InteractionMailbox'; import { WorkspaceToolsState } from './WorkspaceToolsState'; import { SavedRuntimeConnection, @@ -19,6 +21,7 @@ import { ConversationCoreState } from './ConversationCoreState'; */ @ObservedV2 export class RemotePageState { + @Trace interactionMailbox: InteractionMailboxState = new InteractionMailboxState(); readonly workspaceTools: WorkspaceToolsState = new WorkspaceToolsState(); @Trace conversation: ConversationCoreState = new ConversationCoreState('code'); @Trace desktopName: string = ''; @@ -40,6 +43,8 @@ export class RemotePageState { @Trace showRemoteUrlInput: boolean = false; @Trace workspaceName: string = ''; @Trace workspacePath: string = ''; + @Trace workspaceConnectionId: string = ''; + @Trace workspaceSshHost: string = ''; @Trace workspaceBranch: string = ''; @Trace workspaceKind: string = 'normal'; @Trace assistantId: string = ''; @@ -185,10 +190,14 @@ export class RemotePageState { workspacePath: string, assistantId: string, workspaceBranch: string = '', - workspaceKind: string = 'normal' + workspaceKind: string = 'normal', + remoteConnectionId: string = '', + remoteSshHost: string = '' ): void { this.workspaceName = workspaceName; this.workspacePath = workspacePath; + this.workspaceConnectionId = remoteConnectionId; + this.workspaceSshHost = remoteSshHost; this.assistantId = assistantId; this.workspaceBranch = workspaceBranch; this.workspaceKind = workspaceKind; @@ -222,10 +231,12 @@ export class RemotePageState { } setActiveSession(session: SessionSummary): void { + if (this.conversation.activeSession.sessionId !== session.sessionId) this.interactionMailbox = new InteractionMailboxState(); this.conversation.setActiveSession(session); } clearActiveSession(): void { + this.interactionMailbox = new InteractionMailboxState(); this.conversation.clearActiveSession(); this.isLoadingConversation = false; this.pendingSessionId = ''; @@ -253,6 +264,11 @@ export class RemotePageState { this.conversation.setHasMoreMessages(hasMoreMessages); } + setHistoryLoadState(loading: boolean, failed: boolean): void { + this.conversation.historyLoading = loading; + this.conversation.historyFailed = failed; + } + clearTimeline(): void { this.conversation.clearTimeline(); } @@ -336,6 +352,7 @@ export class RemotePageState { /** Clears every conversation-scoped value when its remote identity ends. */ resetConversationContext(): void { + this.interactionMailbox = new InteractionMailboxState(); this.conversation.reset(); this.hasMoreSessions = false; this.isLoadingSessions = false; @@ -380,6 +397,29 @@ export class RemotePageState { return this.visibleSessionsCache; } + private recentHomeSource: RemoteSession[] | undefined = undefined; + private recentHomeCache: RemoteSession[] = []; + + recentHomeSessions(): RemoteSession[] { + const source = this.conversation.sessions; + if (this.recentHomeSource === source) return this.recentHomeCache; + this.recentHomeSource = source; + this.recentHomeCache = source.filter((session: RemoteSession): boolean => + session.id.trim().length > 0 && session.status !== 'archived') + .sort((left: RemoteSession, right: RemoteSession): number => + this.recentSessionTime(right) - this.recentSessionTime(left)) + .filter((session: RemoteSession, index: number, rows: RemoteSession[]): boolean => + rows.findIndex((row: RemoteSession): boolean => row.id === session.id) === index).slice(0, 3); + return this.recentHomeCache; + } + + private recentSessionTime(session: RemoteSession): number { + const updated = TimeFormat.timestampMs(session.updatedAt); + if (Number.isFinite(updated) && updated > 0) return updated; + const created = TimeFormat.timestampMs(session.createdAt); + return Number.isFinite(created) ? created : 0; + } + hasRunningActiveTurn(): boolean { return this.conversation.hasRunningActiveTurn(); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/WorkspaceToolsState.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/WorkspaceToolsState.ets index 98ad2dbacf..fd65ddc454 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/WorkspaceToolsState.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/state/WorkspaceToolsState.ets @@ -1,16 +1,26 @@ +export interface WorkspaceToolLocation { id: string; name: string; } export interface WorkspaceFileEntry { path: string; name: string; isDirectory: boolean; size?: number; modifiedTime?: number; } export interface WorkspaceTerminalEntry { id: string; name?: string; initialCwd?: string; cwd?: string; connectionId?: string; } export interface WorkspaceToolsIntent { type: string; path?: string; text?: string; deviceId?: string; connectionId?: string; tab?: number; } @ObservedV2 export class WorkspaceToolsState { + @Trace fileAction: string = ''; + @Trace fileActionPath: string = ''; + @Trace fileActionName: string = ''; @Trace browsePath: string = ''; @Trace browseEntries: WorkspaceFileEntry[] = []; @Trace browseHasMore: boolean = false; @Trace browseBusy: boolean = false; @Trace browseError: string = ''; @Trace visible: boolean = false; + @Trace deviceScope: boolean = false; + @Trace connectionId: string = ''; + @Trace locations: WorkspaceToolLocation[] = []; @Trace busy: boolean = false; @Trace error: string = ''; + get saveConflict(): boolean { + return this.error.startsWith('FILE_CONFLICT:') || this.error.startsWith('Error: FILE_CONFLICT:'); + } @Trace downloadStatus: string = ''; @Trace uploadPending: boolean = false; @Trace uploadProgress: number = 0; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/ConversationController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/ConversationController.ets index 7eb8f11925..a00854db2b 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/ConversationController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/ConversationController.ets @@ -166,9 +166,10 @@ export class ConversationController { agentType: string = 'code', inPlace: boolean = false, deviceId: string = '', - remoteConnectionId?: string + remoteConnectionId?: string, + remoteSshHost?: string ): Promise { - await this.createFlow.createRemoteSessionInWorkspace(path, agentType, inPlace, deviceId, remoteConnectionId); + await this.createFlow.createRemoteSessionInWorkspace(path, agentType, inPlace, deviceId, remoteConnectionId, remoteSshHost); } async ensureRemoteControlTarget(deviceId?: string): Promise { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/DeviceDirectoryViewModel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/DeviceDirectoryViewModel.ets index 9601853048..d38345f175 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/DeviceDirectoryViewModel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/DeviceDirectoryViewModel.ets @@ -3,12 +3,12 @@ import { AccountDeviceDirectory, AccountDeviceDirectorySource } from '../../services/AccountDeviceDirectory'; -import { CloudAccountBinding, CloudAccountDevice } from '../../services/CloudAccountClient'; +import { CloudAccountBinding, CloudAccountDevice, CloudAccountSession } from '../../services/CloudAccountClient'; import { DeviceDirectoryCoordinator } from '../../services/DeviceDirectoryCoordinator'; import { RemoteI18n } from '../../i18n/RemoteI18n'; import { RemoteLogger } from '../../services/RemoteLogger'; import { RemoteSessionListStore } from '../../services/RemoteSessionListCache'; -import { stampRemoteSessionsDevice } from '../../services/RemoteSessionIdentity'; +import { remoteSessionBelongsToWorkspace, stampRemoteSessionsWorkspace, stampRemoteSessionsDevice } from '../../services/RemoteSessionIdentity'; import { RemoteUiState } from '../../services/RemoteUiState'; import { mapInConcurrentBatches, CONCURRENT_WORKSPACE_LISTINGS } from '../../services/ConcurrentWorkspaceListing'; import { @@ -23,8 +23,14 @@ export interface DeviceDirectoryViewModelHooks { readonly activeDeviceId: () => string; /** Whether the conversation link to [activeDeviceId] is currently live. */ readonly activeDeviceConnected: () => boolean; - readonly loadActiveWorkspaceSessions?: (path: string) => Promise; - readonly onActiveWorkspaceSessions?: (path: string, result: SessionListResult) => void; + readonly loadActiveWorkspaceSessions?: (path: string, remoteConnectionId?: string, remoteSshHost?: string) => Promise; + readonly onActiveWorkspaceSessions?: (path: string, result: SessionListResult, remoteConnectionId?: string, remoteSshHost?: string) => void; +} + +interface DeviceCatalogRequest { + entry: DeviceDirectoryEntry; + session: CloudAccountSession | undefined; + relayUrl: string | undefined; } /** Selection, loading, and retry for the sidebar's device directory. */ @@ -33,7 +39,7 @@ export class DeviceDirectoryViewModel { private readonly store?: RemoteSessionListStore; private readonly hooks: DeviceDirectoryViewModelHooks; private readonly directory?: AccountDeviceDirectorySource; - private readonly inFlight: Set = new Set(); + private readonly inFlight: Map = new Map(); private readonly workspaceInFlight: Map> = new Map>(); /** Device whose control-target catalog request currently owns `loading`. */ @@ -51,6 +57,26 @@ export class DeviceDirectoryViewModel { this.directory = directory; } + async refreshDevices(load: () => Promise): Promise { + if (this.state.refreshing) return; + const binding = this.hooks.credentials(); + if (!binding) return; + const generation = ++this.state.refreshGeneration; + const current = (): boolean => { + const now = this.hooks.credentials(); + return generation === this.state.refreshGeneration && binding.session === now?.session && binding.relayUrl === now?.relayUrl; + }; + this.state.refreshing = true; this.state.refreshError = ''; + try { + await load(); + if (current()) await this.refreshExpandedWorkspaceSessions(); + } catch (error) { + if (current()) this.state.refreshError = error instanceof Error ? error.message : RemoteI18n.t('remote.settings.deviceLoadFailed'); + } finally { + if (generation === this.state.refreshGeneration) this.state.refreshing = false; + } + } + /** * Re-runs the device sync against the current active id. * @@ -92,7 +118,7 @@ export class DeviceDirectoryViewModel { if (workspaces.length === 0 && sessions.length === 0) { return; } - if (workspaces.length > 0) { + if (workspaces.length > 0 && entry.catalogSource.length === 0) { entry.workspaces = workspaces; } if (sessions.length > 0) { @@ -125,6 +151,7 @@ export class DeviceDirectoryViewModel { if (!entry) { return; } + this.inFlight.delete(target); this.liveCatalogDeviceId = target; entry.status = 'loading'; entry.errorText = ''; @@ -156,7 +183,8 @@ export class DeviceDirectoryViewModel { // scan-room -> account-device). The handshake replaces cross-workspace // sessions below, so the old generation's `ready` flags cannot survive it. this.state.resetWorkspaceDirectory(target); - entry.workspaces = workspaces.slice(); + if (entry.catalogSource.length === 0) entry.workspaces = []; + // The workspace catalog is loaded independently of the handshake. // The handshake is the authoritative snapshot for the incoming target. // Merging the row's old cache here makes any historical cross-device // misattribution immortal, because those stale sessions never disappear. @@ -170,13 +198,15 @@ export class DeviceDirectoryViewModel { } /** Publishes the authoritative full catalog returned by the live target. */ - completeLiveCatalog(deviceId: string, workspaces: RecentWorkspaceEntry[]): void { + completeLiveCatalog(deviceId: string, workspaces: RecentWorkspaceEntry[], source?: string, recentWorkspaces?: RecentWorkspaceEntry[]): void { const target = deviceId.trim(); const entry = this.state.find(target); if (!entry) { return; } entry.workspaces = workspaces.slice(); + entry.catalogSource = source || "recent"; + entry.recentWorkspaces = recentWorkspaces || workspaces; entry.status = 'ready'; entry.errorText = ''; if (this.liveCatalogDeviceId === target) { @@ -227,6 +257,8 @@ export class DeviceDirectoryViewModel { existing.deviceName = device.deviceName || existing.deviceName; if (existing.online && !online) { existing.expanded = false; + this.inFlight.delete(device.deviceId); + if (existing.status === 'loading') existing.status = 'idle'; } existing.online = online; return existing; @@ -298,14 +330,14 @@ export class DeviceDirectoryViewModel { * every workspace here was both expensive and the source of unrelated row * updates while the user was only opening one branch. */ - async loadWorkspaceSessions(deviceId: string, path: string): Promise { + async loadWorkspaceSessions(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise { const entry = this.state.find(deviceId); const normalizedPath = path.trim(); const active = deviceId === this.hooks.activeDeviceId() && this.hooks.activeDeviceConnected(); if (normalizedPath.length === 0 || (!active && (!entry || !entry.online))) { return; } - const scope = this.state.workspaceLoadState(deviceId, normalizedPath); + const scope = this.state.workspaceLoadState(deviceId, normalizedPath, remoteConnectionId, remoteSshHost); const pending = this.workspaceInFlight.get(scope); if (pending) { return pending; @@ -329,7 +361,7 @@ export class DeviceDirectoryViewModel { if (!this.state.isCurrentWorkspaceLoad(scope) || !scope.expanded || this.state.selectedDeviceId !== selected) return; try { - await this.loadWorkspaceSessions(scope.deviceId, scope.path); + await this.loadWorkspaceSessions(scope.deviceId, scope.path, scope.remoteConnectionId, scope.remoteSshHost); } catch (_err) { // Each failed branch exposes its own retry row and retains its cache. } @@ -352,16 +384,16 @@ export class DeviceDirectoryViewModel { scope.status = 'loading'; try { if (active && this.hooks.loadActiveWorkspaceSessions && this.hooks.onActiveWorkspaceSessions) { - const result = await this.hooks.loadActiveWorkspaceSessions(path); + const result = await this.hooks.loadActiveWorkspaceSessions(path, scope.remoteConnectionId, scope.remoteSshHost); if (!isCurrent()) return; - this.captureWorkspaceSessions(scope.deviceId, path, result.sessions); - this.hooks.onActiveWorkspaceSessions(path, result); + this.captureWorkspaceSessions(scope.deviceId, path, result.sessions, scope.remoteConnectionId, scope.remoteSshHost); + this.hooks.onActiveWorkspaceSessions(path, result, scope.remoteConnectionId, scope.remoteSshHost); } else { const source = this.directorySource(); if (!source || !entry) throw new Error('Account device directory is unavailable.'); - const sessions = await new DeviceDirectoryCoordinator(source).loadWorkspaceSessions(scope.deviceId, path); + const sessions = await new DeviceDirectoryCoordinator(source).loadWorkspaceSessions(scope.deviceId, path, scope.remoteConnectionId, scope.remoteSshHost); if (!isCurrent()) return; - entry.sessions = DeviceDirectoryViewModel.replaceWorkspaceSessions(entry.sessions, path, sessions); + entry.sessions = DeviceDirectoryViewModel.replaceWorkspaceSessions(entry.sessions, path, sessions, scope.remoteConnectionId, scope.remoteSshHost, entry.workspaces); await this.persistDevice(scope.deviceId, entry.workspaces, entry.sessions); } if (isCurrent()) { @@ -381,7 +413,7 @@ export class DeviceDirectoryViewModel { } /** Replaces one refreshed workspace while preserving every cached sibling. */ - captureWorkspaceSessions(deviceId: string, path: string, sessions: RemoteSession[]): void { + captureWorkspaceSessions(deviceId: string, path: string, sessions: RemoteSession[], remoteConnectionId?: string, remoteSshHost?: string): void { const entry = this.state.find(deviceId.trim()); const normalizedPath = path.trim(); if (!entry || normalizedPath.length === 0) { @@ -390,7 +422,7 @@ export class DeviceDirectoryViewModel { entry.sessions = DeviceDirectoryViewModel.replaceWorkspaceSessions( entry.sessions, normalizedPath, - stampRemoteSessionsDevice(sessions, deviceId.trim()) + stampRemoteSessionsDevice(stampRemoteSessionsWorkspace(sessions, normalizedPath, remoteConnectionId, remoteSshHost), deviceId.trim()), remoteConnectionId, remoteSshHost, entry.workspaces ); } @@ -432,7 +464,10 @@ export class DeviceDirectoryViewModel { if (!entry.online) { return; } - if (this.inFlight.has(deviceId)) { + const binding = this.hooks.credentials(); + const pending = this.inFlight.get(deviceId); + if (pending && pending.entry === entry && pending.session === binding?.session && + pending.relayUrl === binding?.relayUrl) { return; } const source = this.directorySource(); @@ -441,16 +476,30 @@ export class DeviceDirectoryViewModel { entry.errorText = RemoteI18n.t('remote.settings.accountNotSignedIn'); return; } - this.inFlight.add(deviceId); + const request: DeviceCatalogRequest = { + entry, session: binding?.session, relayUrl: binding?.relayUrl + }; + this.inFlight.set(deviceId, request); + const isCurrent = (): boolean => { + const currentBinding = this.hooks.credentials(); + return this.inFlight.get(deviceId) === request && this.state.find(deviceId) === entry && + entry.online && request.session === currentBinding?.session && + request.relayUrl === currentBinding?.relayUrl && deviceId !== this.hooks.activeDeviceId(); + }; entry.status = 'loading'; entry.errorText = ''; try { const coordinator = new DeviceDirectoryCoordinator(source); - entry.workspaces = await coordinator.loadWorkspaces(deviceId); + const catalog = await coordinator.loadWorkspaceCatalog(deviceId); + if (!isCurrent()) return; + entry.workspaces = catalog.workspaces; + entry.catalogSource = catalog.source; + entry.recentWorkspaces = catalog.recentWorkspaces; entry.status = 'ready'; entry.errorText = ''; await this.persistDevice(deviceId, entry.workspaces, entry.sessions); } catch (err) { + if (!isCurrent()) return; // The retry row carries a localized line of its own. What the transport // throws is English and frequently a stack, so it stays in the log, which // is the only place it reads as anything but noise. @@ -458,22 +507,31 @@ export class DeviceDirectoryViewModel { entry.status = 'failed'; entry.errorText = ''; } finally { - this.inFlight.delete(deviceId); + if (this.inFlight.get(deviceId) === request) { + this.inFlight.delete(deviceId); + if (entry.status === 'loading') entry.status = 'idle'; + } } } private async hydrateDevice(entry: DeviceDirectoryEntry): Promise { + const binding = this.hooks.credentials(); + const previousWorkspaces = entry.workspaces; + const previousSessions = entry.sessions; const workspaces = this.store ? await DeviceDirectoryViewModel.safeWorkspaces(this.store, entry.deviceId) : []; const sessions = this.store ? await DeviceDirectoryViewModel.safeSessions(this.store, entry.deviceId) : []; + const currentBinding = this.hooks.credentials(); + if (this.state.find(entry.deviceId) !== entry || binding?.session !== currentBinding?.session || + binding?.relayUrl !== currentBinding?.relayUrl) return; if (workspaces.length === 0 && sessions.length === 0) { return; } - if (workspaces.length > 0) { + if (workspaces.length > 0 && entry.catalogSource.length === 0 && entry.workspaces === previousWorkspaces) { entry.workspaces = workspaces; } - if (sessions.length > 0) { + if (sessions.length > 0 && entry.sessions === previousSessions) { entry.sessions = stampRemoteSessionsDevice(sessions, entry.deviceId); } if (entry.status === 'idle') { @@ -531,11 +589,10 @@ export class DeviceDirectoryViewModel { private static replaceWorkspaceSessions( current: RemoteSession[], path: string, - replacement: RemoteSession[] + replacement: RemoteSession[], remoteConnectionId?: string, remoteSshHost?: string, catalog: RecentWorkspaceEntry[] = [] ): RemoteSession[] { - const target = path.trim(); const preserved = current.filter((item: RemoteSession): boolean => { - return (item.workspacePath || '').trim() !== target; + return !remoteSessionBelongsToWorkspace(item, { path, remoteConnectionId, remoteSshHost }, catalog); }); return replacement.concat(preserved); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteActivityViewModel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteActivityViewModel.ets index fe1daee058..9362e47666 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteActivityViewModel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteActivityViewModel.ets @@ -31,7 +31,7 @@ export class RemoteActivityViewModel { private readonly connection: RemoteConnectionCoordinator; private readonly gate: AsyncLifecycleGate; private readonly hooks: RemoteActivityViewModelHooks; - private recovering: boolean = false; + private recoveryToken: number = 0; constructor( activity: RemoteActivityLifecycleController, @@ -46,8 +46,8 @@ export class RemoteActivityViewModel { } startHeartbeat(): void { - // Engine.IO owns heartbeat/reconnect; a second periodic host RPC is redundant. - this.activity.stopHeartbeat(); + // Relay liveness does not prove that the selected host can answer commands. + this.activity.startHeartbeat(); this.hooks.onObserveCatalog?.(); } @@ -56,42 +56,43 @@ export class RemoteActivityViewModel { this.hooks.onStopCatalog?.(); } - /** Explicit foreground/target recovery probe. Engine.IO and presence events - * own connection monitoring; this method is not scheduled on an interval. */ + /** Bounded host probe used by the foreground heartbeat and explicit recovery. */ async checkConnectionHealth(): Promise { - if (this.hooks.isBusy() || this.recovering || !this.hooks.hasRemoteBinding()) { + if (this.hooks.isBusy() || this.recoveryToken !== 0 || !this.hooks.hasRemoteBinding()) { return; } const wasConnected = this.hooks.isConnected(); if (!wasConnected && !this.hooks.isReconnecting()) { return; } - if (!wasConnected) { - const token = this.gate.begin(); - this.recovering = true; - try { - await this.reconnect(token); - } catch (err) { - if (this.gate.isCurrent(token)) { - this.hooks.onStatus(ConnectionErrorPolicy.errorText(err)); - } - } finally { - this.recovering = false; - } - return; - } + const token = this.gate.begin(); + this.recoveryToken = token; try { await this.connection.ping(); + if (!this.gate.isCurrent(token)) return; + if (!wasConnected) { + this.hooks.onConnectionState('connected'); + this.hooks.onStatus(RemoteI18n.t('connection.connected')); + if (this.hooks.isRemoteChat()) this.hooks.onStartPolling(); + } } catch (err) { + if (!this.gate.isCurrent(token)) { + return; + } if (await this.hooks.onConnectionError(err)) { return; } + if (!this.gate.isCurrent(token)) { + return; + } this.hooks.onStatus(ConnectionErrorPolicy.errorText(err)); if (wasConnected) { RemoteLogger.info('heartbeat ping failed, waiting for the link to come back'); this.hooks.onStopPolling(); this.hooks.onConnectionState('reconnecting'); } + } finally { + if (this.recoveryToken === token) this.recoveryToken = 0; } } @@ -113,16 +114,10 @@ export class RemoteActivityViewModel { } const token = this.gate.begin(); try { - this.hooks.onConnectionState('reconnecting'); await this.connection.ping(); if (!this.gate.isCurrent(token)) { return; } - this.hooks.onConnectionState('connected'); - this.hooks.onStatus(RemoteI18n.t('connection.connected')); - if (this.hooks.isRemoteChat() && this.hooks.activeSession().sessionId.length > 0) { - await this.hooks.onPoll(); - } } catch (err) { if (!this.gate.isCurrent(token)) { return; @@ -130,24 +125,44 @@ export class RemoteActivityViewModel { if (await this.hooks.onConnectionError(err)) { return; } + if (!this.gate.isCurrent(token)) { + return; + } this.hooks.onStatus(ConnectionErrorPolicy.errorText(err)); this.hooks.onConnectionState('reconnecting'); - // The heartbeat keeps running through this. If the reconnect below fails - // there is nothing else left to try again, and its next tick is the retry - // โ€” it just has to stay out of the way while this one is in flight. - this.recovering = true; + // Only a failed host probe starts recovery; foregrounding a healthy + // connection must not flash a reconnect warning. + this.recoveryToken = token; try { await this.reconnect(token); } catch (reconnectErr) { - this.hooks.onStatus(ConnectionErrorPolicy.errorText(reconnectErr)); + if (this.gate.isCurrent(token)) { + this.hooks.onStatus(ConnectionErrorPolicy.errorText(reconnectErr)); + } } finally { - this.recovering = false; + if (this.recoveryToken === token) { + this.recoveryToken = 0; + } + } + return; + } + this.hooks.onConnectionState('connected'); + this.hooks.onStatus(RemoteI18n.t('connection.connected')); + if (this.hooks.isRemoteChat() && this.hooks.activeSession().sessionId.length > 0) { + try { + await this.hooks.onPoll(); + } catch (err) { + // A transcript refresh error is not evidence that the host ping failed. + if (this.gate.isCurrent(token)) { + this.hooks.onStatus(ConnectionErrorPolicy.errorText(err)); + } } } } invalidate(): void { this.gate.invalidate(); + this.recoveryToken = 0; this.hooks.onStopPolling(); this.stopHeartbeat(); } @@ -155,6 +170,7 @@ export class RemoteActivityViewModel { /** Keeps only the active-turn poll alive during a transient background window. */ suspendKeepingActiveTurnPoll(): void { this.gate.invalidate(); + this.recoveryToken = 0; this.stopHeartbeat(); } @@ -167,6 +183,9 @@ export class RemoteActivityViewModel { } if (shouldRestore) { await this.hooks.onRestoreSession(session); + if (!this.gate.isCurrent(token)) { + return; + } this.hooks.onStartPolling(); await this.hooks.onPoll(); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteConnectionController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteConnectionController.ets index c68ce5fe86..75cf9f52b2 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteConnectionController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteConnectionController.ets @@ -228,7 +228,7 @@ export class RemoteConnectionController { applyWorkspace(workspace: WorkspaceInfo): void { this.pageState.setWorkspace(workspace.name, workspace.path, workspace.assistantId || '', workspace.gitBranch, - workspace.workspaceKind || 'normal'); + workspace.workspaceKind || 'normal', workspace.remoteConnectionId || '', workspace.remoteSshHost || ''); } private applyRemoteUrl(remoteUrl: string): void { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteCreateFlowController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteCreateFlowController.ets index cad51f9021..3cc905512e 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteCreateFlowController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteCreateFlowController.ets @@ -78,7 +78,8 @@ export class RemoteCreateFlowController { agentType: string = 'code', inPlace: boolean = false, deviceId: string = '', - remoteConnectionId?: string + remoteConnectionId?: string, + remoteSshHost?: string ): Promise { const runtime = requireRemoteRuntime(this.remoteRuntime); runtime.filePreview.close(); @@ -89,7 +90,8 @@ export class RemoteCreateFlowController { agentType, inPlace ? (sessionId: string): void => this.navigateInPlace(sessionId) : (sessionId: string): void => this.navigateCreated(sessionId), - '', true, remoteConnectionId + '', true, remoteConnectionId, + remoteSshHost ); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteSessionViewModel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteSessionViewModel.ets index cfa7f77fc5..1fce560ede 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteSessionViewModel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteSessionViewModel.ets @@ -127,7 +127,8 @@ export class RemoteSessionViewModel { onRouteChat: (sessionId: string) => void, modelId: string, workspacePath: string = '', - remoteConnectionId?: string + remoteConnectionId?: string, + remoteSshHost?: string ): Promise { const created = await this.sessions.create( agentType, @@ -153,7 +154,8 @@ export class RemoteSessionViewModel { instruction, modelId, workspacePath, - remoteConnectionId + remoteConnectionId, + remoteSshHost ); if (created && (created.initialTurnId || '').length > 0) { // The first poll may have observed the newly allocated session before @@ -172,7 +174,8 @@ export class RemoteSessionViewModel { onRouteChat: (sessionId: string) => void = this.hooks.onRouteChat, modelId: string = '', routeBeforeCreate: boolean = true, - remoteConnectionId?: string + remoteConnectionId?: string, + remoteSshHost?: string ): Promise { if (this.hooks.isBusy() || !this.hooks.remoteAvailable()) { return; @@ -181,7 +184,7 @@ export class RemoteSessionViewModel { this.beginSessionCreation(onRouteChat, routeBeforeCreate); let created: SessionSummary | undefined; try { - created = await this.performCreateSession(agentType, instruction, onRouteChat, modelId, path, remoteConnectionId); + created = await this.performCreateSession(agentType, instruction, onRouteChat, modelId, path, remoteConnectionId, remoteSshHost); if (!created && routeBeforeCreate) { this.restoreAfterFailedCreation(previousSession, onRouteChat); } @@ -222,6 +225,7 @@ export class RemoteSessionViewModel { title: session.title, workspacePath: session.workspacePath, remoteConnectionId: session.remoteConnectionId, + remoteSshHost: session.remoteSshHost, agentType: session.agentType, initialTurnId: session.initialTurnId }; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteWorkspaceViewModel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteWorkspaceViewModel.ets index 28f53a03f6..9299d6dc07 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteWorkspaceViewModel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/RemoteWorkspaceViewModel.ets @@ -13,7 +13,7 @@ export interface RemoteWorkspaceViewModelHooks { readonly onStatus: (statusText: string) => void; readonly onWorkspaceSelected: (workspace: WorkspaceInfo) => void; readonly onCatalogLoading: (targetId: string) => void; - readonly onCatalogLoaded: (targetId: string, workspaces: RecentWorkspaceEntry[]) => void; + readonly onCatalogLoaded: (targetId: string, workspaces: RecentWorkspaceEntry[], source?: string, recentWorkspaces?: RecentWorkspaceEntry[]) => void; readonly onCatalogFailed: (targetId: string) => void; readonly onRefreshSessions: () => Promise; readonly onConnectionFailure: (error: Object) => void; @@ -56,8 +56,8 @@ export class RemoteWorkspaceViewModel { await this.loadAssistants(); } - async selectWorkspace(path: string, connectionId?: string): Promise { - return await this.select(path, false, connectionId); + async selectWorkspace(path: string, connectionId?: string, sshHost?: string): Promise { + return await this.select(path, false, connectionId, sshHost); } async selectAssistant(path: string): Promise { @@ -72,41 +72,27 @@ export class RemoteWorkspaceViewModel { } this.hooks.onCatalogLoading(targetId); try { - this.pageState.savedConnections = []; - this.pageState.savedConnectionsTargetId = ''; + if (this.pageState.savedConnectionsTargetId !== targetId) { + this.pageState.savedConnections = []; + this.pageState.savedConnectionsTargetId = ''; + } try { const saved = await this.coordinator.savedConnections(); - if (targetId === this.hooks.remoteTargetId()) { + if (this.isCurrentCatalogLoad(loadVersion, targetId)) { this.pageState.savedConnections = saved; this.pageState.savedConnectionsTargetId = targetId; } - } catch (error) { this.hooks.onConnectionFailure(error); } - const recent = await this.coordinator.recentWorkspaces(); - if (!this.isCurrentCatalogLoad(loadVersion, targetId)) { - return; + } catch (error) { + if (this.isCurrentCatalogLoad(loadVersion, targetId)) this.hooks.onConnectionFailure(error); } - const assistants = await this.coordinator.assistants(); - if (!this.isCurrentCatalogLoad(loadVersion, targetId)) { - return; - } - const assistantWorkspaces: RecentWorkspaceEntry[] = []; - assistants.forEach((item) => { - assistantWorkspaces.push({ - path: item.path, - name: item.name, - lastOpened: '', - workspaceKind: 'assistant' - } as RecentWorkspaceEntry); - }); - const allWorkspaces = recent.slice(); - assistantWorkspaces.forEach((item: RecentWorkspaceEntry) => { - if (!allWorkspaces.some((existing: RecentWorkspaceEntry) => existing.path === item.path && - (existing.remoteConnectionId || '') === (item.remoteConnectionId || ''))) { - allWorkspaces.push(item); - } - }); - this.pageState.setRecentWorkspaces(allWorkspaces); - this.hooks.onCatalogLoaded(targetId, allWorkspaces); + // The coordinator follows the selected runtime. A superseded request + // must not issue its next operation against a newly selected device. + if (!this.isCurrentCatalogLoad(loadVersion, targetId)) return; + const catalog = await this.coordinator.workspaceCatalog(); + if (!this.isCurrentCatalogLoad(loadVersion, targetId)) return; + const allWorkspaces = catalog.workspaces; + this.pageState.setRecentWorkspaces(catalog.recentWorkspaces); + this.hooks.onCatalogLoaded(targetId, allWorkspaces, catalog.source, catalog.recentWorkspaces); RemoteLogger.info( `workspace catalog loaded target=${targetId} count=${allWorkspaces.length} first=${allWorkspaces.length > 0 ? allWorkspaces[0].path : ''}` ); @@ -178,7 +164,7 @@ export class RemoteWorkspaceViewModel { } } - private async select(path: string, assistant: boolean, connectionId?: string): Promise { + private async select(path: string, assistant: boolean, connectionId?: string, sshHost?: string): Promise { if (this.hooks.isBusy() || path.trim().length === 0 || !this.hooks.isRemoteAvailable()) { return false; } @@ -187,7 +173,7 @@ export class RemoteWorkspaceViewModel { this.hooks.onStatus(RemoteI18n.t(assistant ? 'status.switchingAssistant' : 'status.switchingWorkspace')); const workspace = assistant ? - await this.coordinator.switchAssistant(path) : await this.coordinator.switchWorkspace(path, connectionId); + await this.coordinator.switchAssistant(path) : await this.coordinator.switchWorkspace(path, connectionId, sshHost); this.hooks.onWorkspaceSelected(workspace); this.pageState.closePickers(); this.pageState.clear(); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/SettingsController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/SettingsController.ets index c398741455..74a7cef15b 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/SettingsController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/SettingsController.ets @@ -548,7 +548,9 @@ export class SettingsController { initialSync.workspace.path, initialSync.workspace.assistantId || '', initialSync.workspace.gitBranch, - initialSync.workspace.workspaceKind || 'normal' + initialSync.workspace.workspaceKind || 'normal', + initialSync.workspace.remoteConnectionId || '', + initialSync.workspace.remoteSshHost || '' ); cloud.remoteState.setSessions( stampRemoteSessionsDevice(initialSync.sessions, deviceId), @@ -700,9 +702,12 @@ export class SettingsController { // Relay credentials are database-scoped; never replay an older credential at a new endpoint. this.selectedRelayUrl = OFFICIAL_RELAY_URL; this.cloudRelayUrl = OFFICIAL_RELAY_URL; - cloud.remoteState.setAccountUserId(persisted.userId); + // Saved profile identity is not authentication against the current relay. + // Keep the encrypted record and display name, but expose the sign-in entry. + cloud.remoteState.setAccountUserId(''); cloud.remoteState.setAccountUsername(persisted.username || persisted.userId); cloud.remoteState.setStatusText(RemoteI18n.t('remote.settings.relayReauthorizationRequired')); + RemoteLogger.warn('Saved account requires authorization for the current relay'); return; } const session: CloudAccountSession = { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets index d7eea8c22a..4d691e57b0 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets @@ -3,8 +3,9 @@ import { WorkspaceFileUploadClient } from '../../services/WorkspaceFileUploadCli import { Encoding } from '../../services/Encoding'; import { RemoteSessionManager } from '../../services/RemoteSessionManager'; import { DurableSessionStream } from '../../services/DurableSessionStream'; -import { WorkspaceToolsState, WorkspaceToolsIntent, WorkspaceFileEntry, WorkspaceTerminalEntry } from '../state/WorkspaceToolsState'; -import { RuntimeFileLocation as RuntimeLocation } from '../../model/RemoteModels'; +import { WorkspaceToolsState, WorkspaceToolsIntent, WorkspaceFileEntry, WorkspaceTerminalEntry, WorkspaceToolLocation } from '../state/WorkspaceToolsState'; +import { RuntimeFileLocation } from '../../model/RemoteModels'; + interface RuntimeSystemInfo { homeDir?: string; } interface DirectoryPage { children: WorkspaceFileEntry[]; hasMore: boolean; } interface TerminalPage { data: string; nextOffset: number; cursor: number; truncated: boolean; } @@ -21,7 +22,7 @@ export class WorkspaceToolsViewModel { private state: WorkspaceToolsState; private manager: RemoteSessionManager; private target: () => string; - private location?: RuntimeLocation; + private location?: RuntimeFileLocation; private deviceScope: boolean = false; private download?: RemoteFileDownloadController; private upload: WorkspaceFileUploadClient; @@ -32,8 +33,10 @@ export class WorkspaceToolsViewModel { private epoch: number = 0; private stream?: DurableSessionStream; private terminalCursor: number = 0; + private terminalGeneration: number = 0; private readingTerminal: boolean = false; private terminalDirty: boolean = false; + private terminalHistoryError: string = ''; private inputBuffer: string = ''; private inputTimer: number = -1; private inputSending: boolean = false; @@ -43,6 +46,15 @@ export class WorkspaceToolsViewModel { this.upload = new WorkspaceFileUploadClient(manager, target); } dispatch(intent: WorkspaceToolsIntent): void { + if (intent.type === 'file-action-cancel') { if (!this.state.busy) this.state.fileAction = ''; return; } + if (intent.type === 'file-action-name') { if (!this.state.busy) this.state.fileActionName = intent.text || ''; return; } + if (intent.type === 'file-action') { + if (this.state.busy) return; + this.state.fileAction = intent.text || ''; this.state.fileActionPath = intent.path || ''; + this.state.fileActionName = this.state.entries.find((entry: WorkspaceFileEntry): boolean => entry.path === intent.path)?.name || ''; + this.state.error = ''; return; + } + if (intent.type === 'panel') { this.state.initialTab = intent.tab || 0; return; } if (intent.type === 'browse-workspace' || intent.type === 'browse-more') { void this.browseWorkspace(intent); return; } if (intent.type === 'discard-cancel') { this.pendingNavigation = undefined; this.state.confirmDiscard = false; return; } if (intent.type === 'discard-confirm') { @@ -131,7 +143,7 @@ export class WorkspaceToolsViewModel { private close(): void { this.clearInput(); this.download?.cancel(); this.download = undefined; - this.epoch++; this.stream?.close(); this.stream = undefined; this.state.visible = false; + this.epoch++; this.terminalGeneration++; this.stream?.close(); this.stream = undefined; this.state.visible = false; this.state.fileAction = ''; } private current(epoch: number): boolean { return epoch === this.epoch && this.owner === this.target() && this.state.visible; } private async run(intent: WorkspaceToolsIntent): Promise { @@ -150,6 +162,17 @@ export class WorkspaceToolsViewModel { if (opening) { if (intent.deviceId && intent.deviceId !== this.owner) throw new Error('The controlled runtime changed'); this.deviceScope = intent.type === 'open-device'; + this.state.deviceScope = this.deviceScope; + this.state.connectionId = intent.connectionId || ''; + if (this.deviceScope) { + const emptyArgs: Record = {}; + const locations = await this.manager.hostInvoke('ssh_list_saved_connections', emptyArgs); + if (!this.current(epoch)) return; + this.state.locations = locations; + if (intent.connectionId && !locations.some((item: WorkspaceToolLocation): boolean => item.id === intent.connectionId)) { + throw new Error('Saved connection is unavailable on the controlled runtime'); + } + } let path = intent.path || ''; if (this.deviceScope) { if (intent.connectionId) path = '/'; @@ -161,7 +184,7 @@ export class WorkspaceToolsViewModel { } } if (!path) throw new Error('The runtime did not provide a default directory'); - const workspace: RuntimeLocation = { path: path, remoteConnectionId: intent.connectionId }; + const workspace: RuntimeFileLocation = { path: path, remoteConnectionId: intent.connectionId }; this.state.initialTab = intent.tab || 0; this.location = workspace; this.state.directory = workspace.path; await this.directory(workspace.path, false, epoch); @@ -179,7 +202,7 @@ export class WorkspaceToolsViewModel { if (this.current(epoch)) { this.state.selectedFile = path; this.state.content = content; this.originalContent = content; this.expectedHash = hash; this.state.editorVisible = true; this.state.editing = false; this.state.dirty = false; } } else if (intent.type === 'download') { if (!this.location) throw new Error('Select a runtime location first'); - const location: RuntimeLocation = { path: this.state.directory, remoteConnectionId: this.location.remoteConnectionId }; + const location: RuntimeFileLocation = { path: this.state.directory, remoteConnectionId: this.location.remoteConnectionId }; this.download = new RemoteFileDownloadController(this.manager, (_pending: string, _saved: string, status: string): void => { if (this.current(epoch)) this.state.downloadStatus = status; }, (): void => {}, (): void => {}, (): void => {}); @@ -198,6 +221,24 @@ export class WorkspaceToolsViewModel { await this.directory(this.state.directory, false, epoch); } else if (intent.type === 'upload-cancel') { await this.upload.cancel(); if (this.current(epoch)) this.state.uploadPending = false; + } else if (intent.type === 'file-action-submit') { + const action = this.state.fileAction; + const name = this.state.fileActionName; + const oldPath = this.state.fileActionPath; + if (action !== 'delete' && !name.trim()) throw new Error('A file name is required'); + const path = name.startsWith('/') ? name : `${this.state.directory.replace(/\/$/, '')}/${name}`; + const entry = this.state.entries.find((item: WorkspaceFileEntry): boolean => item.path === oldPath); + if ((action === 'rename' || action === 'delete') && !entry) throw new Error('File entry is no longer available'); + if (action === 'file') await this.file('write_file_content', { filePath: path, workspacePath: this.location?.path, content: '', expectedHash: '' }); + else if (action === 'folder') await this.file('create_directory', { path: path }); + else if (action === 'rename') await this.file('rename_file', { oldPath: oldPath, newPath: path }); + else if (action === 'delete') await this.file(entry?.isDirectory ? 'delete_directory' : 'delete_file', { path: oldPath, recursive: false }); + else if (action === 'upload') { + await this.upload.select(path, this.location?.remoteConnectionId, '', () => this.current(epoch), this.location?.path || ''); + await this.upload.resume(() => this.current(epoch), (offset: number, total: number) => { if (this.current(epoch)) this.state.uploadProgress = total ? Math.floor(offset * 100 / total) : 100; }); + } else throw new Error('Unknown file action'); + await this.directory(this.state.directory, false, epoch); + if (this.current(epoch)) this.state.fileAction = ''; } else if (intent.type === 'mkdir') { await this.file('create_directory', { path: intent.path }); await this.directory(this.state.directory, false, epoch); @@ -223,7 +264,7 @@ export class WorkspaceToolsViewModel { const args: TerminalArgs = { request: { sessionId: this.state.terminalId, immediate: true } }; await this.manager.hostInvoke('terminal_close', args); if (this.current(epoch)) { - this.stream?.close(); this.stream = undefined; + this.terminalGeneration++; this.stream?.close(); this.stream = undefined; this.state.terminals = this.state.terminals.filter((entry: WorkspaceTerminalEntry) => entry.id !== this.state.terminalId); this.state.terminalId = ''; this.state.output = ''; } @@ -255,35 +296,48 @@ export class WorkspaceToolsViewModel { } private attach(id: string): void { this.clearInput(); + const generation = ++this.terminalGeneration; this.stream?.close(); this.state.terminalId = id; this.state.output = ''; this.terminalCursor = 0; const epoch = this.epoch; this.stream = this.manager.subscribeSession(`terminal-${id}`, { - onEvent: async (): Promise => { if (this.current(epoch)) this.terminalDirty = true; }, - onCaughtUp: async (): Promise => { this.terminalDirty = true; await this.readTerminal(id, epoch); }, - onError: (error: Error): void => { if (this.current(epoch)) this.state.error = error.message; } + onEvent: async (): Promise => { if (this.currentTerminal(id, epoch, generation)) this.terminalDirty = true; }, + onCaughtUp: async (): Promise => { + if (!this.currentTerminal(id, epoch, generation)) return; + this.terminalDirty = true; await this.readTerminal(id, epoch, generation); + }, + onError: (error: Error): void => { if (this.currentTerminal(id, epoch, generation)) { this.terminalHistoryError = error.message; this.state.error = error.message; } } }); this.terminalDirty = true; - void this.readTerminal(id, epoch); + void this.readTerminal(id, epoch, generation); } - private async readTerminal(id: string, epoch: number): Promise { + private currentTerminal(id: string, epoch: number, generation: number): boolean { + return this.current(epoch) && generation === this.terminalGeneration && id === this.state.terminalId; + } + private async readTerminal(id: string, epoch: number, generation: number): Promise { + if (!this.currentTerminal(id, epoch, generation)) return; if (this.readingTerminal) { this.terminalDirty = true; return; } this.readingTerminal = true; try { - while (this.terminalDirty && this.current(epoch) && id === this.state.terminalId) { + while (this.terminalDirty && this.currentTerminal(id, epoch, generation)) { this.terminalDirty = false; const args: TerminalHistoryArgs = { sessionId: id, afterOffset: this.terminalCursor }; const page = await this.manager.hostInvoke('terminal_get_history', args); - if (!this.current(epoch) || id !== this.state.terminalId) return; + if (!this.currentTerminal(id, epoch, generation)) return; + if (!Number.isSafeInteger(page.nextOffset) || !Number.isSafeInteger(page.cursor) || page.nextOffset < 0 || page.cursor < page.nextOffset) throw new Error('Invalid terminal history cursor'); + if (page.nextOffset < this.terminalCursor && !page.truncated) throw new Error('Terminal cursor moved backwards'); + if (!(page.nextOffset > this.terminalCursor || (page.truncated && page.nextOffset < this.terminalCursor) || page.nextOffset === page.cursor)) throw new Error('Terminal history made no progress'); + if (this.terminalHistoryError.length > 0 && this.state.error === this.terminalHistoryError) this.state.error = ''; + this.terminalHistoryError = ''; this.state.output = (page.truncated ? '' : this.state.output) + page.data; // Match the runtime's retained display history; the durable stream stores notifications only. if (this.state.output.length > 4 * 1024 * 1024) this.state.output = this.state.output.slice(-4 * 1024 * 1024); this.terminalCursor = page.nextOffset; this.terminalDirty = this.terminalDirty || page.nextOffset < page.cursor; } - } catch (error) { if (this.current(epoch)) this.state.error = String(error); } + } catch (error) { if (this.currentTerminal(id, epoch, generation)) { this.terminalHistoryError = String(error); this.state.error = this.terminalHistoryError; } } finally { this.readingTerminal = false; - if (this.terminalDirty && this.state.visible && this.state.terminalId !== id) void this.readTerminal(this.state.terminalId, this.epoch); + if (this.terminalDirty && this.state.visible && generation !== this.terminalGeneration) void this.readTerminal(this.state.terminalId, this.epoch, this.terminalGeneration); } } } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/AccountDeviceDirectory.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/AccountDeviceDirectory.ets index 76883b55d4..e52e660335 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/AccountDeviceDirectory.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/AccountDeviceDirectory.ets @@ -1,3 +1,4 @@ +import { WorkspaceCatalog, projectWorkspaceCatalog } from './WorkspaceCatalog'; import { AssistantEntry, AssistantListResponse, @@ -16,9 +17,10 @@ import { RemoteResponseMapper } from './RemoteResponseMapper'; import { stampRemoteSessionsDevice, stampRemoteSessionsWorkspace } from './RemoteSessionIdentity'; export interface AccountDeviceDirectorySource { + listWorkspaceCatalog(deviceId: string): Promise; listRecentWorkspaces(deviceId: string): Promise; listAssistants(deviceId: string): Promise; - listSessions(deviceId: string, workspacePath: string): Promise; + listSessions(deviceId: string, workspacePath: string, remoteConnectionId?: string, remoteSshHost?: string): Promise; } /** Read-only per-device RPCs that never touch the active conversation transport. */ @@ -29,6 +31,11 @@ export class AccountDeviceDirectory implements AccountDeviceDirectorySource { this.context = context; } + async listWorkspaceCatalog(deviceId: string): Promise { + const response = await this.transport(deviceId).send(RemoteCommandFactory.listRecentWorkspaces()); + return projectWorkspaceCatalog(response, await this.listAssistants(deviceId)); + } + async listRecentWorkspaces(deviceId: string): Promise { const response = await this.transport(deviceId).send( RemoteCommandFactory.listRecentWorkspaces() @@ -43,18 +50,18 @@ export class AccountDeviceDirectory implements AccountDeviceDirectorySource { return response.assistants || []; } - async listSessions(deviceId: string, workspacePath: string): Promise { + async listSessions(deviceId: string, workspacePath: string, remoteConnectionId?: string, remoteSshHost?: string): Promise { const path = workspacePath.trim(); if (path.length === 0) { return []; } const response = await this.transport(deviceId).send( - RemoteCommandFactory.listSessions(path, 50, 0, ''), + RemoteCommandFactory.listSessions(path, 50, 0, '', remoteConnectionId, remoteSshHost), INTERACTIVE_DIRECTORY_TIMEOUT_MS ); const sessions = stampRemoteSessionsWorkspace( RemoteResponseMapper.sessions(response.sessions || []), - path + path, remoteConnectionId, remoteSshHost ); return stampRemoteSessionsDevice(sessions, deviceId); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/ChatSessionController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/ChatSessionController.ets index b270e73613..f97ea839c0 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/ChatSessionController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/ChatSessionController.ets @@ -1,4 +1,5 @@ -import { PermissionControlOverlay, SessionInteractionMailbox } from './PermissionControlOverlay'; +import { InteractionMailboxStore } from './InteractionMailboxStore'; +import { InteractionMailboxAction, InteractionMailboxState } from '../model/InteractionMailbox'; import { DurableSessionReducer } from './DurableSessionReducer'; import { DurableSessionStream, DurableSessionEvent } from './DurableSessionStream'; import { @@ -31,10 +32,13 @@ export interface ChatSessionSnapshot { /** Exact completed turn whose terminal assistant is now durable. */ completedTurnId: string; hasMoreMessages?: boolean; + historyLoading?: boolean; + historyFailed?: boolean; } export interface ChatSessionControllerCallbacks { onSnapshot: (snapshot: ChatSessionSnapshot) => void; + onMailbox?: (state: InteractionMailboxState) => void; onError: (error: Object) => void; canPoll: (sessionId: string) => boolean; } @@ -47,8 +51,6 @@ export interface ChatSessionControllerOptions { /** Like Happy's normalized message reducer, history and realtime are one * stable-id collection. No separately timed transcript snapshot is merged in. */ -interface MailboxRequest { sessionId: string; } -interface MailboxArgs { request: MailboxRequest; } export class ChatSessionController { private sessionManager: RemoteSessionManager; @@ -57,75 +59,88 @@ export class ChatSessionController { private cursor: ChatSessionCursor = { pollVersion: 0, knownMessageCount: 0, knownModelCatalogVersion: 0 }; private stream?: DurableSessionStream; private reducer: DurableSessionReducer = new DurableSessionReducer(); - private controls: PermissionControlOverlay = new PermissionControlOverlay(); + private mailbox: InteractionMailboxStore; private generation: number = 0; private stopped: boolean = true; private caughtUp: boolean = false; private sessionState: string = 'idle'; private completedTurnId: string = ''; private hasMoreMessages: boolean = false; - private mailboxRead?: Promise; - private mailboxVersion: number = 0; + private historyLoading: boolean = false; + private historyFailed: boolean = false; + private modelReadVersion: number = 0; + private pendingModelCatalog?: RemoteModelCatalog; constructor(sessionManager: RemoteSessionManager, callbacks: ChatSessionControllerCallbacks, _options?: ChatSessionControllerOptions) { this.sessionManager = sessionManager; this.callbacks = callbacks; + this.mailbox = new InteractionMailboxStore(sessionManager, (state: InteractionMailboxState): void => { this.callbacks.onMailbox?.(state); }); } start(sessionId: string, cursor: ChatSessionCursor, _activeTurn?: ChatMessage): void { if (!this.stopped && this.sessionId === sessionId && this.stream && !this.stream.isClosed()) { this.stream.wake(); return; } this.stop(); this.sessionId = sessionId; this.cursor = cursor; - this.reducer = new DurableSessionReducer(); this.controls = new PermissionControlOverlay(); this.stopped = false; this.caughtUp = false; + this.reducer = new DurableSessionReducer(); this.mailbox.select(sessionId); this.stopped = false; this.caughtUp = false; this.sessionState = 'idle'; this.completedTurnId = ''; this.hasMoreMessages = false; + this.historyLoading = false; this.historyFailed = false; + this.pendingModelCatalog = undefined; const epoch = this.generation; + void this.loadInitialModelCatalog(sessionId, epoch); try { this.stream = this.sessionManager.subscribeSession(sessionId, { onEvent: async (event: DurableSessionEvent): Promise => { if (!this.current(epoch) || event.session_id !== sessionId) return; - if (event.event === 'session-interaction-changed') { await this.refreshMailbox(sessionId, epoch); return; } - const controlChanged = this.controls.apply(event); - if (!this.reducer.apply(event) && !controlChanged) return; + // Mailbox RPCs are independently coalesced and generation-fenced. They + // must not delay the serial transcript stream or its durable cursor. + if (event.event === 'session-interaction-changed') { void this.mailbox.refresh(); return; } + if (!this.reducer.apply(event)) return; if (this.caughtUp) this.publish(); }, - onResumed: async (): Promise => { await this.refreshMailbox(sessionId, epoch); }, + onResumed: async (): Promise => { if (this.current(epoch)) void this.mailbox.refresh(); }, onCaughtUp: async (): Promise => { if (!this.current(epoch)) return; this.caughtUp = true; this.publish(); }, onHistory: (hasMore: boolean): void => { this.hasMoreMessages = hasMore; }, + onHistoryState: (loading: boolean, failed: boolean): void => { + if (!this.current(epoch)) return; + this.historyLoading = loading; this.historyFailed = failed; + if (this.caughtUp) this.publish(); + }, onError: (error: Error): void => { if (this.current(epoch)) this.callbacks.onError(error); } }); } catch (error) { this.callbacks.onError(error); } } + async refreshModelCatalog(): Promise { + if (!this.stopped) await this.loadInitialModelCatalog(this.sessionId, this.generation); + } + private async loadInitialModelCatalog(sessionId: string, epoch: number): Promise { + const version = ++this.modelReadVersion; + try { + const catalog = await this.sessionManager.getModelCatalog(sessionId); + if (!this.current(epoch) || version !== this.modelReadVersion) return; + this.pendingModelCatalog = catalog; + if (this.caughtUp) this.publish(); + } catch (error) { + if (this.current(epoch) && version === this.modelReadVersion) this.callbacks.onError(error); + } + } stop(_clearActiveTurn: boolean = true): void { - this.generation++; this.stopped = true; this.mailboxRead = undefined; this.stream?.close(); this.stream = undefined; + this.generation++; this.stopped = true; this.mailbox.select(''); this.stream?.close(); this.stream = undefined; } loadOlder(): void { this.stream?.loadOlder(); } nudge(): void { this.stream?.wake(); } updateCursor(cursor: ChatSessionCursor): void { this.cursor = cursor; } - updateKnownModelCatalogVersion(version: number): void { this.cursor.knownModelCatalogVersion = version; } + updateKnownModelCatalogVersion(version: number): void { + this.modelReadVersion++; + this.pendingModelCatalog = undefined; + this.cursor.knownModelCatalogVersion = version; + } setKnownMessageCount(count: number): void { this.cursor.knownMessageCount = count; } clearActiveTurn(): void { /* Runtime message/state events own turn lifetime. */ } - private refreshMailbox(sessionId: string, epoch: number): Promise { - this.mailboxVersion++; - if (this.mailboxRead) return this.mailboxRead; - const read = this.readMailbox(sessionId, epoch).finally(() => { if (this.mailboxRead === read) this.mailboxRead = undefined; }); - this.mailboxRead = read; return read; - } - private async readMailbox(sessionId: string, epoch: number): Promise { - try { - while (this.current(epoch)) { - const version = this.mailboxVersion; const revision = this.controls.revision; - const args: MailboxArgs = { request: { sessionId: sessionId } }; - const pending = await this.sessionManager.hostInvoke('get_session_interaction_mailbox', args); - if (!this.current(epoch)) return; - if (version !== this.mailboxVersion || revision !== this.controls.revision) continue; - this.controls.hydrateMailbox(pending); - if (this.caughtUp) this.publish(); - return; - } - } catch (error) { if (this.current(epoch)) this.callbacks.onError(error); } + async dispatchMailbox(action: InteractionMailboxAction): Promise { + if (!this.stopped && action.sessionId === this.sessionId) await this.mailbox.dispatch(action); } private current(epoch: number): boolean { return epoch === this.generation && !this.stopped; } async pollNow(): Promise { this.stream?.wake(); } @@ -133,7 +148,7 @@ export class ChatSessionController { if (this.stopped || !this.callbacks.canPoll(this.sessionId)) return; const persisted: ChatMessage[] = []; let active: ChatMessage | undefined = undefined; - this.controls.decorate(this.reducer.messages()).forEach((message: ChatMessage) => { + this.reducer.messages().forEach((message: ChatMessage) => { if (message.role === 'assistant' && (message.status === 'active' || message.status === 'running')) active = message; else { persisted.push(message); @@ -141,11 +156,15 @@ export class ChatSessionController { } }); this.cursor.knownMessageCount = persisted.length; + const catalog = this.pendingModelCatalog; + this.pendingModelCatalog = undefined; + if (catalog) this.cursor.knownModelCatalogVersion = catalog.version || 0; this.callbacks.onSnapshot({ - sessionId: this.sessionId, cursor: this.cursor, changed: true, title: '', + sessionId: this.sessionId, cursor: this.cursor, changed: true, title: '', modelCatalog: catalog, sessionState: active ? 'active' : this.sessionState, newMessages: [], activeTurn: active, messageSnapshot: persisted, shouldSyncAfterTurnEnded: false, - historyRewritten: false, completedTurnId: this.completedTurnId, hasMoreMessages: this.hasMoreMessages + historyRewritten: false, completedTurnId: this.completedTurnId, hasMoreMessages: this.hasMoreMessages, + historyLoading: this.historyLoading, historyFailed: this.historyFailed }); } } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/DeviceDirectoryCoordinator.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/DeviceDirectoryCoordinator.ets index b48c91fef6..52b3eb8ea6 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/DeviceDirectoryCoordinator.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/DeviceDirectoryCoordinator.ets @@ -1,3 +1,4 @@ +import { WorkspaceCatalog, mergeLegacyWorkspaces } from './WorkspaceCatalog'; import { AssistantEntry, RecentWorkspaceEntry, RemoteSession } from '../model/RemoteModels'; import { AccountDeviceDirectorySource } from './AccountDeviceDirectory'; import { @@ -40,11 +41,11 @@ export class DeviceDirectoryCoordinator { } async loadWorkspaces(deviceId: string): Promise { - const listed = await Promise.all([ - this.directory.listRecentWorkspaces(deviceId), - this.directory.listAssistants(deviceId) - ]); - return DeviceDirectoryCoordinator.mergeWorkspaces(listed[0], listed[1]); + return (await this.loadWorkspaceCatalog(deviceId)).workspaces; + } + + async loadWorkspaceCatalog(deviceId: string): Promise { + return await this.directory.listWorkspaceCatalog(deviceId); } async loadSessions( @@ -54,7 +55,7 @@ export class DeviceDirectoryCoordinator { const groups = await mapInConcurrentBatches( workspaces, (entry: RecentWorkspaceEntry): Promise => { - return this.safeSessionsForWorkspace(deviceId, entry.path); + return this.safeSessionsForWorkspace(deviceId, entry.path, entry.remoteConnectionId, entry.remoteSshHost); }, this.concurrency ); @@ -62,13 +63,13 @@ export class DeviceDirectoryCoordinator { } /** One explicit workspace listing; used by the lazy sidebar disclosure. */ - async loadWorkspaceSessions(deviceId: string, path: string): Promise { - return stampRemoteSessionsWorkspace(await this.directory.listSessions(deviceId, path), path); + async loadWorkspaceSessions(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise { + return stampRemoteSessionsWorkspace(await this.directory.listSessions(deviceId, path, remoteConnectionId, remoteSshHost), path, remoteConnectionId, remoteSshHost); } - private async safeSessionsForWorkspace(deviceId: string, path: string): Promise { + private async safeSessionsForWorkspace(deviceId: string, path: string, remoteConnectionId?: string, remoteSshHost?: string): Promise { try { - return await this.loadWorkspaceSessions(deviceId, path); + return await this.loadWorkspaceSessions(deviceId, path, remoteConnectionId, remoteSshHost); } catch (err) { // One unreadable workspace is not a reason to lose the other twenty. RemoteLogger.warn(`load workspace sessions failed device=${deviceId} path=${path}: ${String(err)}`); @@ -80,18 +81,6 @@ export class DeviceDirectoryCoordinator { recent: RecentWorkspaceEntry[], assistants: AssistantEntry[] ): RecentWorkspaceEntry[] { - const all = recent.slice(); - assistants.forEach((item: AssistantEntry) => { - if (all.some((existing: RecentWorkspaceEntry) => existing.path === item.path)) { - return; - } - all.push({ - path: item.path, - name: item.name, - lastOpened: '', - workspaceKind: 'assistant' - }); - }); - return all; + return mergeLegacyWorkspaces(recent, assistants); } } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionReducer.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionReducer.ets index 0c492a84ca..9ba5aa5fad 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionReducer.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionReducer.ets @@ -9,7 +9,7 @@ interface ToolCall { id: string; input: Object; } interface ToolResult { result: Object; success: boolean; error?: string; durationMs?: number; } interface ItemData { id: string; content?: string; timestamp?: number; orderIndex?: number; - status?: string; isStreaming?: boolean; isSubagentItem?: boolean; parentTaskToolId?: string; + status?: string; isStreaming?: boolean; isSubagentItem?: boolean; parentTaskToolId?: string; subagentSessionId?: string; attemptId?: string; attemptIndex?: number; interruptionReason?: string; toolName?: string; toolCall?: ToolCall; toolResult?: ToolResult; startTime?: number; durationMs?: number; } @@ -24,17 +24,20 @@ interface StoredItem { revision: number; turnId: string; roundId: string; data: * second wire DTO or overwrite newer parent state with an older child record. */ export class DurableSessionReducer { private records: Map = new Map(); + private tombstones: Map = new Map(); private turns: Map = new Map(); private rounds: Map = new Map(); private items: Map = new Map(); apply(event: DurableSessionEvent): boolean { if (event.event !== 'session-record') return false; const record = event.payload as SessionRecord; - if (record.sessionId !== event.session_id || !record.id || !Number.isSafeInteger(record.revision) || record.revision < 0) throw new Error('Invalid session record identity'); + if (record.sessionId !== event.session_id || !record.id || !Number.isSafeInteger(record.revision) || record.revision < 1) throw new Error('Invalid session record identity'); const previous = this.records.get(record.id); if (previous && previous.revision >= record.revision) return false; if (record.deleted) { + if (!/^(turn|round|item)\/.+/.test(record.id)) throw new Error('Invalid session tombstone identity'); this.records.set(record.id, record); + this.tombstones.set(record.id, record.revision); if (record.id.startsWith('turn/')) { const id = record.id.slice(5); const previousTurn = this.turns.get(id); if (!previousTurn || previousTurn.revision < record.revision) this.turns.set(id, { revision: record.revision }); @@ -76,7 +79,13 @@ export class DurableSessionReducer { const userText = metadata?.original_text || turn.userMessage.content; if (userText.length > 0 || (metadata?.images?.length || 0) > 0) result.push({ id: turn.userMessage.id, turnId: turn.turnId, role: 'user', text: userText, images: metadata?.images, status: 'sent', timestamp: new Date(turn.userMessage.timestamp).toISOString() }); - const entries = Array.from(this.items.values()).filter((item: StoredItem) => item.turnId === turn.turnId && !!this.rounds.get(item.roundId)?.data) + const turnFence = this.tombstones.get(`turn/${turn.turnId}`) ?? 0; + const entries = Array.from(this.items.values()).filter((item: StoredItem) => { + const round = this.rounds.get(item.roundId); + const roundFence = Math.max(turnFence, this.tombstones.get(`round/${item.roundId}`) ?? 0); + return item.turnId === turn.turnId && !!round?.data && round.revision > roundFence && + item.revision > Math.max(roundFence, this.tombstones.get(`item/${item.data.data.id}`) ?? 0); + }) .sort((a: StoredItem, b: StoredItem) => { const roundA = this.rounds.get(a.roundId)?.data?.roundIndex || 0; const roundB = this.rounds.get(b.roundId)?.data?.roundIndex || 0; @@ -87,12 +96,13 @@ export class DurableSessionReducer { const tools: RemoteToolStatusResponse[] = []; for (const entry of entries) { const data = entry.data.data; + if (data.status === 'superseded' || data.status === 'retry_superseded') continue; const round = this.rounds.get(entry.roundId)?.data?.roundIndex || 0; - const item: ChatMessageItemResponse = { type: entry.data.type, content: data.content, round_index: round, is_subagent: data.isSubagentItem }; + const item: ChatMessageItemResponse = { type: entry.data.type, content: data.content, round_index: round, is_subagent: data.isSubagentItem === true || !!data.subagentSessionId }; if (entry.data.type === 'tool') { - const tool: RemoteToolStatusResponse = { id: data.id, name: data.toolName, status: data.status || (data.toolResult ? (data.toolResult.success ? 'completed' : 'failed') : 'running'), + const tool: RemoteToolStatusResponse = { id: data.toolCall?.id || data.id, name: data.toolName, status: data.status || (data.toolResult ? (data.toolResult.success ? 'completed' : 'failed') : 'running'), tool_input: data.toolCall?.input, tool_output: data.toolResult?.result, - duration_ms: data.durationMs || data.toolResult?.durationMs, error_preview: data.toolResult?.error, start_ms: data.startTime }; + duration_ms: data.durationMs ?? data.toolResult?.durationMs, error_preview: data.toolResult?.error, start_ms: data.startTime }; item.tool = tool; tools.push(tool); } items.push(item); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionStream.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionStream.ets index 60e89018dc..5a163c20cb 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionStream.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/DurableSessionStream.ets @@ -28,6 +28,7 @@ export interface DurableSessionCallbacks { onResumed?: () => Promise; onError: (error: Error) => void; onHistory?: (hasMore: boolean) => void; + onHistoryState?: (loading: boolean, failed: boolean) => void; } /** Account WS provides wakeups; the encrypted durable log is the authority. @@ -50,6 +51,7 @@ export class DurableSessionStream { private fragments: Uint8Array[] = []; private pending: Map = new Map(); private retryTimer: number = -1; + private historyPrefetchTimer: number = -1; private retryMs: number = 1000; private unsubscribeUpdate: () => void; private unsubscribeReconnect: () => void; @@ -84,29 +86,60 @@ export class DurableSessionStream { this.unsubscribeUpdate(); this.unsubscribeReconnect(); if (this.retryTimer !== -1) clearTimeout(this.retryTimer); + this.cancelHistoryPrefetch(); this.fragments = []; this.pending.clear(); this.grant = undefined; } loadOlder(): void { - if (this.stopped || !this.hasOlder || this.historyRequested) return; + if (this.stopped || !this.hasOlder) return; + this.cancelHistoryPrefetch(); + if (this.historyRequested) { + if (this.retryTimer !== -1) { + clearTimeout(this.retryTimer); this.retryTimer = -1; + this.callbacks.onHistoryState?.(true, false); this.wake(); + } + return; + } this.historyRequested = true; + this.callbacks.onHistoryState?.(true, false); if (!this.running) this.catchUp(); } + private cancelHistoryPrefetch(): void { + if (this.historyPrefetchTimer !== -1) clearTimeout(this.historyPrefetchTimer); + this.historyPrefetchTimer = -1; + } + + private scheduleHistoryPrefetch(): void { + if (this.stopped || !this.hasOlder || this.historyRequested || + this.historyPrefetchTimer !== -1 || this.logicalId === '@host/catalog') return; + // Match mobile-web: render the newest page first, then use the same + // serialized history request for background prefetch and user scrolls. + this.historyPrefetchTimer = setTimeout(() => { + this.historyPrefetchTimer = -1; + this.loadOlder(); + }, 250); + } + private async backward(before: number): Promise { if (!this.source.readBefore || !this.grant) throw new Error('History pagination is unavailable'); let page = await this.source.readBefore(this.grant.relay_session_id, before); let rows = page.messages.slice().sort((a: SessionMessage, b: SessionMessage) => a.seq - b.seq); + if (rows.length > 0 && rows[rows.length - 1].seq >= before) throw new Error('History pagination did not advance'); // A wire page may start in the middle of a large record. Fetch only its // missing prefix before applying; never discard a partial event. while (rows.length > 0) { const first = Encoding.parseJsonObject(rows[0].content.c); if (first.index === 0) break; if (!page.hasMore) throw new Error('Session history has an incomplete fragment prefix'); - page = await this.source.readBefore(this.grant.relay_session_id, rows[0].seq); + const boundary = rows[0].seq; + page = await this.source.readBefore(this.grant.relay_session_id, boundary); if (page.messages.length === 0) throw new Error('Session history pagination stalled'); + if (page.messages.some((message: SessionMessage): boolean => message.seq >= boundary)) { + throw new Error('History pagination did not advance'); + } rows = page.messages.concat(rows).sort((a: SessionMessage, b: SessionMessage) => a.seq - b.seq); } return { messages: rows, hasMore: page.hasMore }; @@ -195,16 +228,23 @@ export class DurableSessionStream { more = page.hasMore; if (more && page.messages.length === 0) throw new Error('Session log returned an empty continuation'); } - if (this.historyRequested) { await this.applyHistory(false); this.historyRequested = false; } + if (this.historyRequested) { + this.callbacks.onHistoryState?.(true, false); + await this.applyHistory(false); this.historyRequested = false; + this.callbacks.onHistoryState?.(false, false); + } if (this.pending.size > 0 && !this.pending.has(this.readSeq + 1)) throw new Error('Session log sequence gap'); if (!this.stopped && this.fragments.length === 0) { if (this.resumed) { await this.callbacks.onResumed?.(); this.resumed = false; } await this.callbacks.onCaughtUp(); + this.scheduleHistoryPrefetch(); } this.retryMs = 1000; } } catch (error) { if (!this.stopped) { + this.cancelHistoryPrefetch(); + if (this.historyRequested) this.callbacks.onHistoryState?.(false, true); this.dirty = true; this.pending.clear(); this.readSeq = this.committedSeq; diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/HostCatalogObserver.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/HostCatalogObserver.ets index ee8efabeab..cf828778d5 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/HostCatalogObserver.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/HostCatalogObserver.ets @@ -17,8 +17,8 @@ export class HostCatalogObserver { start(target: string): void { if (!target) { this.stop(); return; } if (this.target === target && this.stream && !this.stream.isClosed()) { - this.dirty = true; - void this.flush(this.generation); + // Successful session reads also ensure observation is active. Treating + // that as an invalidation makes refresh -> start -> refresh a loop. this.stream.wake(); return; } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/InteractionMailboxStore.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/InteractionMailboxStore.ets new file mode 100644 index 0000000000..235211a06c --- /dev/null +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/InteractionMailboxStore.ets @@ -0,0 +1,121 @@ +import { + InteractionMailboxAction, InteractionMailboxActionType, InteractionMailboxState, + PendingPermission, PendingQuestion, SessionInteractionMailbox +} from '../model/InteractionMailbox'; +import { RemoteSessionManager } from './RemoteSessionManager'; + +interface MailboxRequest { sessionId: string; } +interface MailboxArgs { request: MailboxRequest; } +interface PermissionReply { requestId: string; reply: string; updatedInput?: Object; } +interface PermissionReplyArgs { request: PermissionReply; } + +/** The runtime mailbox owns approval and question state, never transcript events. */ +export class InteractionMailboxStore { + private client: RemoteSessionManager; + private onChange: (state: InteractionMailboxState) => void; + private state: InteractionMailboxState = new InteractionMailboxState(); + private epoch: number = 0; + private version: number = 0; + private read?: Promise; + private interacting: Set = new Set(); + + constructor(client: RemoteSessionManager, onChange: (state: InteractionMailboxState) => void) { + this.client = client; this.onChange = onChange; + } + + select(sessionId: string): void { + this.epoch++; this.version++; this.read = undefined; this.interacting.clear(); + this.state = new InteractionMailboxState(); this.state.sessionId = sessionId; + this.publish(); + } + + refresh(): Promise { + if (!this.state.sessionId) return Promise.resolve(); + this.version++; + if (this.read) return this.read; + const epoch = this.epoch; + const read = this.load(epoch).finally(() => { if (this.read === read) this.read = undefined; }); + this.read = read; + return read; + } + + private async load(epoch: number): Promise { + const sessionId = this.state.sessionId; + try { + while (epoch === this.epoch) { + const version = this.version; + const args: MailboxArgs = { request: { sessionId } }; + let snapshot: SessionInteractionMailbox; + try { + snapshot = await this.client.hostInvoke('get_session_interaction_mailbox', args); + } catch (error) { + if (epoch !== this.epoch) return; + // A newer invalidation owns the next read even if this request fails. + if (version !== this.version) continue; + throw error as Error; + } + if (epoch !== this.epoch) return; + if (version !== this.version) continue; + if (snapshot.sessionId !== sessionId || !Array.isArray(snapshot.permissions?.requests) || + !Array.isArray(snapshot.userQuestions?.questions)) throw new Error('Interaction mailbox session or payload mismatch'); + this.state.requests = snapshot.permissions.requests.filter((request: PendingPermission): boolean => request.sessionId === sessionId); + this.state.questions = snapshot.userQuestions.questions.filter((question: PendingQuestion): boolean => question.sessionId === sessionId); + const owned = new Set(this.state.ownedToolIds); + this.state.questions.forEach((question: PendingQuestion): void => { owned.add(question.toolId); }); + this.state.requests.forEach((request: PendingPermission): void => { if (request.toolCallId) owned.add(request.toolCallId); }); + this.state.ownedToolIds = Array.from(owned); + this.state.failure = ''; this.publish(); return; + } + } catch (_error) { + if (epoch === this.epoch) { this.state.failure = 'load'; this.publish(); } + } + } + + async dispatch(action: InteractionMailboxAction): Promise { + if (!action.sessionId || action.sessionId !== this.state.sessionId) return; + if (action.type === InteractionMailboxActionType.Refresh) { await this.refresh(); return; } + const id = action.id || ''; + const permission = action.type === InteractionMailboxActionType.Approve || action.type === InteractionMailboxActionType.Reject; + if (permission ? !this.state.requests.some((item: PendingPermission): boolean => item.requestId === id) : + !this.state.questions.some((item: PendingQuestion): boolean => item.toolId === id)) return; + const epoch = this.epoch; + if (action.type === InteractionMailboxActionType.StartQuestion) { + if (this.interacting.has(id)) return; + this.interacting.add(id); + try { await this.client.startQuestionInteraction(action.sessionId, id); } + catch (_error) { if (epoch === this.epoch) { this.interacting.delete(id); this.state.failure = 'reply'; this.publish(); } } + return; + } + if (this.state.busy) return; + this.state.busy = true; this.state.failure = ''; this.publish(); + try { + if (permission) { + // Rejection never consumes an approval draft, including an invalid one. + const input = action.type === InteractionMailboxActionType.Approve ? action.updatedInput : undefined; + if (input !== undefined && (input === null || typeof input !== 'object' || Array.isArray(input))) { + throw new Error('Permission input must be a JSON object'); + } + const args: PermissionReplyArgs = { request: { requestId: id, + reply: action.type === InteractionMailboxActionType.Approve ? 'once' : 'reject', + updatedInput: action.type === InteractionMailboxActionType.Approve ? input : undefined } }; + await this.client.hostInvoke('respond_permission', args); + } else if (action.type === InteractionMailboxActionType.Answer && action.answers) { + await this.client.answerQuestion(id, action.answers); + } else return; + if (epoch === this.epoch) await this.refresh(); + } catch (_error) { + if (epoch === this.epoch) { this.state.failure = 'reply'; this.publish(); } + } finally { + if (epoch === this.epoch) { this.state.busy = false; this.publish(); } + } + } + + private publish(): void { + const snapshot = new InteractionMailboxState(); + snapshot.sessionId = this.state.sessionId; + snapshot.requests = this.state.requests.slice(); snapshot.questions = this.state.questions.slice(); + snapshot.ownedToolIds = this.state.ownedToolIds.slice(); + snapshot.busy = this.state.busy; snapshot.failure = this.state.failure; + this.onChange(snapshot); + } +} diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/PermissionControlOverlay.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/PermissionControlOverlay.ets deleted file mode 100644 index a6d88e0a42..0000000000 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/PermissionControlOverlay.ets +++ /dev/null @@ -1,93 +0,0 @@ -import { ChatMessage, RemoteToolStatusResponse, ChatMessageItemResponse } from '../model/RemoteModels'; -import { DurableSessionEvent } from './DurableSessionStream'; - -interface ToolControlEvent { turnId?: string; toolEvent?: ToolControlPayload; } -interface ToolControlPayload { event_type?: string; tool_id?: string; tool_name?: string; params?: Object; } -export interface PendingPermission { requestId: string; sessionId: string; roundId?: string; toolCallId?: string; source: PermissionSource; } -export interface SessionInteractionMailbox { sessionId: string; userQuestions: QuestionMailbox; permissions: PermissionMailbox; } -interface QuestionMailbox { revision: number; questions: PendingQuestion[]; } -interface PermissionMailbox { revision: number; requests: PendingPermission[]; } -interface PendingQuestion { toolId: string; sessionId: string; dialogTurnId?: string; modelRoundId?: string; questions: Object; registeredAtMs: number; interactionStarted?: boolean; } -interface PermissionSource { identity: string; } -interface PendingControl { turnId: string; tool: RemoteToolStatusResponse; } - -/** Permission mailbox is control state, never a transcript or text delta. */ -export class PermissionControlOverlay { - private pending: Map = new Map(); - revision: number = 0; - apply(event: DurableSessionEvent): boolean { - if (event.event !== 'agentic://tool-event') return false; - const payload = event.payload as ToolControlEvent; - const tool = payload.toolEvent; - if (!tool?.tool_id) return false; - if (tool.event_type === 'ConfirmationNeeded') { - this.pending.set(tool.tool_id, { turnId: payload.turnId || '', tool: { id: tool.tool_id, name: tool.tool_name, status: 'pending_confirmation', tool_input: tool.params } }); - } else if (['Confirmed', 'Completed', 'Failed', 'Cancelled', 'Rejected'].includes(tool.event_type || '')) { - const keys: string[] = []; - this.pending.forEach((control: PendingControl, key: string) => { if (control.tool.id === tool.tool_id) keys.push(key); }); - keys.forEach((key: string) => this.pending.delete(key)); - } else return false; - this.revision++; return true; - } - hydrate(sessionId: string, requests: PendingPermission[]): void { - const next: Map = new Map(); - for (const request of requests) { - if (request.sessionId !== sessionId) continue; - const id = request.toolCallId || request.requestId; - const old = this.pending.get(request.requestId) || this.pending.get(id); - const control: PendingControl = old ? JSON.parse(JSON.stringify(old)) as PendingControl : { turnId: '', tool: { id: id, name: request.source.identity, status: 'pending_confirmation' } }; - control.tool.permission_request_id = request.requestId; - next.set(request.requestId, control); - } - this.pending = next; this.revision++; - } - hydrateMailbox(snapshot: SessionInteractionMailbox): void { - this.hydrate(snapshot.sessionId, snapshot.permissions.requests); - for (const question of snapshot.userQuestions.questions) { - this.pending.set(`question:${question.toolId}`, { turnId: question.dialogTurnId || '', tool: { - id: question.toolId, name: 'AskUserQuestion', status: 'running', tool_input: question.questions - } }); - } - } - decorate(messages: ChatMessage[]): ChatMessage[] { - const finished: string[] = []; - for (const message of messages) { - for (const tool of message.tools || []) { - if (['completed', 'failed', 'cancelled', 'rejected', 'success', 'error'].includes(tool.status || '')) { - this.pending.forEach((control: PendingControl, key: string) => { if (control.tool.id === tool.id) finished.push(key); }); - } - } - } - finished.forEach((key: string) => this.pending.delete(key)); - const active = messages.find((message: ChatMessage) => message.role === 'assistant' && ['active', 'running'].includes(message.status)); - const source = messages.slice(); - if (!active && this.pending.size > 0) source.push({ id: 'permission-mailbox', role: 'assistant', text: '', status: 'active', tools: [], items: [] }); - return source.map((message: ChatMessage): ChatMessage => { - if (message.role !== 'assistant') return message; - const controls: PendingControl[] = Array.from(this.pending.values()).filter((control: PendingControl) => message.id === 'permission-mailbox' || (control.turnId && source.some((row: ChatMessage) => row.turnId === control.turnId) ? control.turnId === message.turnId : message === active)); - if (!['active', 'running'].includes(message.status)) { - controls.forEach((control: PendingControl) => { if (!control.tool.permission_request_id) this.pending.delete(control.tool.id || ''); }); - return message; - } - const tools = (message.tools || []).slice(); - const items = (message.items || []).slice(); - for (const control of controls) { - const index = tools.findIndex((tool: RemoteToolStatusResponse) => tool.id === control.tool.id); - const original = index >= 0 ? tools[index] : undefined; - const replacement = JSON.parse(JSON.stringify(original || control.tool)) as RemoteToolStatusResponse; - replacement.id = control.tool.id; replacement.name = control.tool.name || original?.name; - replacement.permission_request_id = control.tool.permission_request_id; - replacement.status = control.tool.status; replacement.tool_input = control.tool.tool_input || original?.tool_input; - if (index >= 0) tools[index] = replacement; else tools.push(replacement); - const itemIndex = items.findIndex((item: ChatMessageItemResponse) => item.tool?.id === replacement.id); - if (itemIndex >= 0) { - const item = JSON.parse(JSON.stringify(items[itemIndex])) as ChatMessageItemResponse; - item.tool = replacement; items[itemIndex] = item; - } - else items.push({ type: 'tool', tool: replacement }); - } - const decorated = JSON.parse(JSON.stringify(message)) as ChatMessage; - decorated.tools = tools; decorated.items = items; return decorated; - }); - } -} diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatCommandController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatCommandController.ets index 64f71c759f..19dd11ed12 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatCommandController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatCommandController.ets @@ -96,8 +96,9 @@ export class RemoteChatCommandController { async forgetMessages(sessionId: string): Promise { await this.cache.forget(sessionId); } async loadOlderMessages(sessionId: string, _currentPollVersion: number, - hasMoreMessages: boolean, isBusy: boolean): Promise { - if (!sessionId || !hasMoreMessages || isBusy) return; + hasMoreMessages: boolean, _isBusy: boolean): Promise { + // Active generation does not prevent reading earlier durable records. + if (!sessionId || !hasMoreMessages) return; this.requestHistory(sessionId, true); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatPollingLifecycleController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatPollingLifecycleController.ets index 4c903cd359..35462bdc80 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatPollingLifecycleController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteChatPollingLifecycleController.ets @@ -1,3 +1,4 @@ +import { InteractionMailboxAction } from '../model/InteractionMailbox'; import { ChatMessage } from '../model/RemoteModels'; import { ChatSessionController, @@ -38,6 +39,10 @@ export class RemoteChatPollingLifecycleController { this.controller.stop(); } + async dispatchMailbox(action: InteractionMailboxAction): Promise { await this.controller.dispatchMailbox(action); } + + async refreshModelCatalog(): Promise { await this.controller.refreshModelCatalog(); } + loadOlder(): void { this.controller.loadOlder(); } nudge(): void { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteCommandFactory.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteCommandFactory.ets index dc0fa568df..1c4a8f2eb5 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteCommandFactory.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteCommandFactory.ets @@ -27,7 +27,8 @@ export class RemoteCommandFactory { }; } - static listSessions(workspacePath: string, limit: number, offset: number, query: string): RemoteCommand { + static listSessions(workspacePath: string, limit: number, offset: number, query: string, + remoteConnectionId?: string, remoteSshHost?: string): RemoteCommand { const command: RemoteCommand = { cmd: 'list_sessions', workspace_path: workspacePath, @@ -37,6 +38,8 @@ export class RemoteCommandFactory { if (query.trim().length > 0) { command.query = query.trim(); } + if (remoteConnectionId !== undefined) command.remote_connection_id = remoteConnectionId; + if (remoteSshHost !== undefined) command.remote_ssh_host = remoteSshHost; return command; } @@ -49,7 +52,8 @@ export class RemoteCommandFactory { agent_type: options.agentType, session_name: options.title.trim() || defaultTitle, workspace_path: workspacePath || undefined, - remote_connection_id: workspacePath ? options.remoteConnectionId || '' : undefined + remote_connection_id: workspacePath ? options.remoteConnectionId || '' : undefined, + remote_ssh_host: workspacePath ? options.remoteSshHost : undefined }; } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionController.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionController.ets index fadf60fa5f..98a1492bd0 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionController.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionController.ets @@ -172,7 +172,8 @@ export class RemoteSessionController { instruction: string = '', modelId: string = '', workspacePath: string = '', - remoteConnectionId?: string + remoteConnectionId?: string, + remoteSshHost?: string ): Promise { if (isBusy || !remoteAvailable) { return undefined; @@ -193,7 +194,8 @@ export class RemoteSessionController { instruction, modelId, workspacePath, - remoteConnectionId + remoteConnectionId, + remoteSshHost }, handleAllocated); if (!allocationHandled) { await handleAllocated(session); @@ -234,7 +236,9 @@ export class RemoteSessionController { const session: SessionSummary = { sessionId: item.id, title: item.title, - workspacePath, + workspacePath: item.workspaceIdentity?.path || workspacePath, + remoteConnectionId: item.workspaceIdentity?.remoteConnectionId, + remoteSshHost: item.workspaceIdentity?.remoteSshHost, agentType: item.agentType }; this.callbacks.onActiveSession(session); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionIdentity.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionIdentity.ets index 5f32bb73f2..030e62fb92 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionIdentity.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionIdentity.ets @@ -1,4 +1,23 @@ -import { RemoteSession } from '../model/RemoteModels'; +import { RemoteSession, RemoteWorkspaceIdentity } from '../model/RemoteModels'; + +export function remoteWorkspaceKey(scope: RemoteWorkspaceIdentity): string { + const trimmedPath = scope.path.trim(); + const path = trimmedPath.length > 0 ? trimmedPath.replace(/\/+$/, '') || '/' : ''; + return [scope.remoteConnectionId?.trim() || '', scope.remoteSshHost?.trim() || '', path] + .map((part: string): string => `${part.length}:${part}`).join(''); +} + +/** Unknown legacy sessions are eligible only for an unambiguous local catalog row. */ +export function remoteSessionBelongsToWorkspace( + session: RemoteSession, scope: RemoteWorkspaceIdentity, catalog: RemoteWorkspaceIdentity[] +): boolean { + if (session.workspaceIdentity) return remoteWorkspaceKey(session.workspaceIdentity) === remoteWorkspaceKey(scope); + if (scope.remoteConnectionId?.trim() || scope.remoteSshHost?.trim() || !session.workspacePath?.trim()) return false; + const local: RemoteWorkspaceIdentity = { path: session.workspacePath }; + if (remoteWorkspaceKey(local) !== remoteWorkspaceKey(scope)) return false; + return catalog.filter((item: RemoteWorkspaceIdentity): boolean => + remoteWorkspaceKey({ path: item.path }) === remoteWorkspaceKey(local)).length === 1; +} /** Stable key for a session that may exist on more than one desktop. */ export function remoteSessionIdentity(session: RemoteSession): string { @@ -19,6 +38,7 @@ export function stampRemoteSessionDevice(session: RemoteSession, deviceId: strin messageCount: session.messageCount, workspacePath: session.workspacePath, workspaceName: session.workspaceName, + workspaceIdentity: session.workspaceIdentity, pinned: session.pinned, titleGenerated: session.titleGenerated, deviceId @@ -33,8 +53,9 @@ export function stampRemoteSessionsDevice(sessions: RemoteSession[], deviceId: s } /** Fills the scope omitted by older list_sessions payloads without mutating it. */ -export function stampRemoteSessionWorkspace(session: RemoteSession, workspacePath: string): RemoteSession { - if ((session.workspacePath || '').length > 0 || workspacePath.length === 0) { +export function stampRemoteSessionWorkspace(session: RemoteSession, workspacePath: string, + remoteConnectionId?: string, remoteSshHost?: string): RemoteSession { + if (workspacePath.length === 0 || session.workspaceIdentity) { return session; } return { @@ -45,7 +66,8 @@ export function stampRemoteSessionWorkspace(session: RemoteSession, workspacePat updatedAt: session.updatedAt, createdAt: session.createdAt, messageCount: session.messageCount, - workspacePath, + workspacePath: session.workspacePath || workspacePath, + workspaceIdentity: { path: session.workspacePath || workspacePath, remoteConnectionId, remoteSshHost }, workspaceName: session.workspaceName, pinned: session.pinned, titleGenerated: session.titleGenerated, @@ -55,8 +77,10 @@ export function stampRemoteSessionWorkspace(session: RemoteSession, workspacePat export function stampRemoteSessionsWorkspace( sessions: RemoteSession[], - workspacePath: string + workspacePath: string, + remoteConnectionId?: string, + remoteSshHost?: string ): RemoteSession[] { return sessions.map((item: RemoteSession): RemoteSession => - stampRemoteSessionWorkspace(item, workspacePath)); + stampRemoteSessionWorkspace(item, workspacePath, remoteConnectionId, remoteSshHost)); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets index af70b3ab05..ed02271ec3 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets @@ -1,3 +1,4 @@ +import { WorkspaceCatalog, projectWorkspaceCatalog } from './WorkspaceCatalog'; import { RuntimeFileLocation } from '../model/RemoteModels'; import { DurableSessionCallbacks, DurableSessionStream } from './DurableSessionStream'; import { SavedRuntimeConnection, SavedRuntimeConnectionsResponse } from '../model/RemoteModels'; @@ -97,9 +98,9 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile const sessionResponse = await this.sendOnTransport( transport, generation, - RemoteCommandFactory.listSessions(workspace.path, 50, 0, '') + RemoteCommandFactory.listSessions(workspace.path, 50, 0, '', workspace.remoteConnectionId, workspace.remoteSshHost) ); - const sessions = RemoteResponseMapper.sessions(sessionResponse.sessions || []); + const sessions = stampRemoteSessionsWorkspace(RemoteResponseMapper.sessions(sessionResponse.sessions || []), workspace.path, workspace.remoteConnectionId, workspace.remoteSshHost); this.requireCurrentGeneration(generation); this.transport = transport; this.workspace = workspace; @@ -121,6 +122,11 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile return RemoteResponseMapper.workspaceFromResponse(response); } + async listWorkspaceCatalog(): Promise { + const response = await this.send(RemoteCommandFactory.listRecentWorkspaces()); + return projectWorkspaceCatalog(response, await this.listAssistants()); + } + async listRecentWorkspaces(): Promise { const response = await this.send(RemoteCommandFactory.listRecentWorkspaces()); return RemoteResponseMapper.recentWorkspaces(response.workspaces || []); @@ -133,9 +139,10 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile return response.value || []; } - async setWorkspace(path: string, connectionId?: string): Promise { + async setWorkspace(path: string, connectionId?: string, sshHost?: string): Promise { const command = RemoteCommandFactory.setWorkspace(path); command.remote_connection_id = connectionId; + command.remote_ssh_host = sshHost; const response = await this.send(command); if (response.success !== true) { throw new Error(response.error || 'Failed to set workspace.'); @@ -161,27 +168,29 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile return result.sessions; } - async listSessionsForWorkspace(workspacePath: string, limit: number = 50): Promise { - return (await this.listSessionPageForWorkspace(workspacePath, limit)).sessions; + async listSessionsForWorkspace(workspacePath: string, limit: number = 50, remoteConnectionId?: string, remoteSshHost?: string): Promise { + return (await this.listSessionPageForWorkspace(workspacePath, limit, remoteConnectionId, remoteSshHost)).sessions; } - async listSessionPageForWorkspace(workspacePath: string, limit: number = 50): Promise { + async listSessionPageForWorkspace(workspacePath: string, limit: number = 50, remoteConnectionId?: string, remoteSshHost?: string): Promise { const path = workspacePath.trim(); if (path.length === 0) { return { sessions: [], hasMore: false }; } const response = await this.send( - RemoteCommandFactory.listSessions(path, limit, 0, ''), + RemoteCommandFactory.listSessions(path, limit, 0, '', remoteConnectionId, remoteSshHost), INTERACTIVE_DIRECTORY_TIMEOUT_MS ); return { - sessions: stampRemoteSessionsWorkspace(RemoteResponseMapper.sessions(response.sessions || []), path), + sessions: stampRemoteSessionsWorkspace(RemoteResponseMapper.sessions(response.sessions || []), path, remoteConnectionId, remoteSshHost), hasMore: response.has_more || false }; } async listSessions(limit: number, offset: number, query: string, agentType: string): Promise { const workspacePath = this.workspace?.path || ''; + const connectionId = this.workspace?.remoteConnectionId; + const sshHost = this.workspace?.remoteSshHost; const normalizedAgentType = RemoteSessionManager.normalizedSessionFilter(agentType); const trimmedQuery = query.trim(); const pageSize = normalizedAgentType ? Math.max(100, limit) : Math.max(1, limit); @@ -190,9 +199,9 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile let pageOffset = 0; let hasMore = true; while (hasMore && filteredSessions.length < targetCount) { - const command = RemoteCommandFactory.listSessions(workspacePath, pageSize, pageOffset, trimmedQuery); + const command = RemoteCommandFactory.listSessions(workspacePath, pageSize, pageOffset, trimmedQuery, connectionId, sshHost); const response = await this.send(command); - const sessions = RemoteResponseMapper.allSessions(response.sessions || []); + const sessions = stampRemoteSessionsWorkspace(RemoteResponseMapper.allSessions(response.sessions || []), workspacePath, connectionId, sshHost); for (const session of sessions) { if (RemoteResponseMapper.isVisibleSession(session) && (!normalizedAgentType || RemoteSessionManager.agentMatchesFilter(session.agentType, normalizedAgentType))) { @@ -224,6 +233,8 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile const resolvedPath = response.workspace_path ?? workspacePath; const remoteConnectionId = response.workspace_path !== undefined ? response.remote_connection_id : (requestedPath ? options.remoteConnectionId : undefined); + const remoteSshHost = response.workspace_path !== undefined ? response.remote_ssh_host : + (requestedPath ? options.remoteSshHost : undefined); const normalizedAgentType = options.agentType.toLowerCase(); const fallbackTitle = normalizedAgentType === 'claw' || normalizedAgentType === 'assistant' || normalizedAgentType === 'chat' ? 'Assistant Session' : normalizedAgentType === 'cowork' ? 'Cowork Session' : 'Code Session'; @@ -234,6 +245,7 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile title, workspacePath: resolvedPath, remoteConnectionId, + remoteSshHost, agentType: options.agentType }); } @@ -250,6 +262,7 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile title, workspacePath: resolvedPath, remoteConnectionId, + remoteSshHost, agentType: options.agentType, initialTurnId }; @@ -403,6 +416,11 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile await this.send(RemoteCommandFactory.deleteSession(sessionId)); } + async startQuestionInteraction(sessionId: string, toolId: string): Promise { + const command: RemoteCommand = { cmd: 'start_question_interaction', session_id: sessionId, tool_id: toolId }; + await this.send(command); + } + async confirmTool(toolId: string, updatedInput?: Object): Promise { if (toolId.startsWith('permission:')) { const args: PermissionReplyArgs = { request: { requestId: toolId.slice('permission:'.length), reply: 'once', updatedInput: updatedInput } }; @@ -425,18 +443,31 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile async getPermissionMode(): Promise { const response = await this.send(RemoteCommandFactory.getPermissionMode()); - return response.mode || 'ask'; + if (response.mode !== 'ask' && response.mode !== 'auto' && response.mode !== 'full_access') { + throw new Error('Permission mode unavailable'); + } + return response.mode; } async setPermissionMode(mode: RemotePermissionMode): Promise { const response = await this.send(RemoteCommandFactory.setPermissionMode(mode)); - return response.mode || mode; + if (!response.mode) return this.getPermissionMode(); + if (response.mode !== 'ask' && response.mode !== 'auto' && response.mode !== 'full_access') { + throw new Error('Permission mode unavailable'); + } + return response.mode; } async answerQuestion(toolId: string, answers: RemoteQuestionAnswerPayload): Promise { await this.send(RemoteCommandFactory.answerQuestion(toolId, answers)); } + private currentFileLocation(): RuntimeFileLocation | undefined { + return this.workspace ? { + path: this.workspace.path, remoteConnectionId: this.workspace.remoteConnectionId + } : undefined; + } + private applyFileWorkspace(command: RemoteCommand, sessionId: string | undefined, workspace: RuntimeFileLocation | undefined): void { if (sessionId) return; if (!workspace?.path) throw new Error('File workspace identity is unavailable.'); @@ -446,10 +477,8 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile async getFileInfo(path: string, sessionId?: string): Promise { const command = RemoteCommandFactory.getFileInfo(path, sessionId); - const workspace: RuntimeFileLocation | undefined = this.workspace ? { - path: this.workspace.path, remoteConnectionId: this.workspace.remoteConnectionId - } : undefined; - this.applyFileWorkspace(command, sessionId, workspace); + this.applyFileWorkspace(command, sessionId, this.currentFileLocation()); + const response = await this.send(command); return { name: response.name || RemoteResponseMapper.basename(path), @@ -466,7 +495,7 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile fileWorkspace?: RuntimeFileLocation ): Promise { const command = RemoteCommandFactory.readFileChunk(path, offset, limit, sessionId); - this.applyFileWorkspace(command, sessionId, fileWorkspace || this.workspace); + this.applyFileWorkspace(command, sessionId, fileWorkspace || this.currentFileLocation()); const response = await this.send(command); return { name: response.name || RemoteResponseMapper.basename(path), @@ -545,8 +574,19 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile } async ping(): Promise { - await this.send(RemoteCommandFactory.ping()); - return true; + // Bound key lookup and RPC together, like mobile-web's health probe. + let timer: number = -1; + try { + await Promise.race([ + this.send(RemoteCommandFactory.ping(), 10000), + new Promise((_resolve, reject) => { + timer = setTimeout(() => reject(new Error('Remote health probe timed out')), 10000); + }) + ]); + return true; + } finally { + if (timer !== -1) clearTimeout(timer); + } } /** diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceCoordinator.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceCoordinator.ets index b7f0cadde3..287e5aa201 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceCoordinator.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceCoordinator.ets @@ -1,3 +1,4 @@ +import { WorkspaceCatalog } from './WorkspaceCatalog'; import { SavedRuntimeConnection, AssistantEntry, RecentWorkspaceEntry, RemoteSession, WorkspaceInfo } from '../model/RemoteModels'; import { CONCURRENT_WORKSPACE_LISTINGS, @@ -15,6 +16,10 @@ export class RemoteWorkspaceCoordinator { this.repository = repository; } + async workspaceCatalog(): Promise { + return await this.repository.listWorkspaceCatalog(); + } + async recentWorkspaces(): Promise { return await this.repository.listRecentWorkspaces(); } @@ -25,8 +30,8 @@ export class RemoteWorkspaceCoordinator { async savedConnections(): Promise { return await this.repository.savedConnections(); } - async switchWorkspace(path: string, connectionId?: string): Promise { - return await this.repository.setWorkspace(path, connectionId); + async switchWorkspace(path: string, connectionId?: string, sshHost?: string): Promise { + return await this.repository.setWorkspace(path, connectionId, sshHost); } async switchAssistant(path: string): Promise { diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceRepository.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceRepository.ets index d184adb44c..e041e56e13 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceRepository.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteWorkspaceRepository.ets @@ -1,10 +1,12 @@ +import { WorkspaceCatalog } from './WorkspaceCatalog'; import { SavedRuntimeConnection, AssistantEntry, RecentWorkspaceEntry, RemoteSession, WorkspaceInfo } from '../model/RemoteModels'; import { RemoteSessionManager } from './RemoteSessionManager'; export interface RemoteWorkspaceDataSource { + listWorkspaceCatalog(): Promise; savedConnections(): Promise; listRecentWorkspaces(): Promise; - setWorkspace(path: string, connectionId?: string): Promise; + setWorkspace(path: string, connectionId?: string, sshHost?: string): Promise; listAssistants(): Promise; setAssistant(path: string): Promise; listSessionsForWorkspace(path: string, limit: number): Promise; @@ -22,12 +24,16 @@ export class RemoteWorkspaceRepository implements RemoteWorkspaceDataSource { async savedConnections(): Promise { return await this.sessionManager.savedConnections(); } + async listWorkspaceCatalog(): Promise { + return await this.sessionManager.listWorkspaceCatalog(); + } + async listRecentWorkspaces(): Promise { return await this.sessionManager.listRecentWorkspaces(); } - async setWorkspace(path: string, connectionId?: string): Promise { - await this.sessionManager.setWorkspace(path, connectionId); + async setWorkspace(path: string, connectionId?: string, sshHost?: string): Promise { + await this.sessionManager.setWorkspace(path, connectionId, sshHost); return await this.sessionManager.getWorkspaceInfo(); } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceCatalog.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceCatalog.ets new file mode 100644 index 0000000000..c24634853a --- /dev/null +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceCatalog.ets @@ -0,0 +1,48 @@ +import { AssistantEntry, RecentWorkspaceEntry, RecentWorkspaceListResponse } from '../model/RemoteModels'; +import { RemoteResponseMapper } from './RemoteResponseMapper'; + +export type WorkspaceCatalogSource = 'opened' | 'recent'; + +export interface WorkspaceCatalog { + workspaces: RecentWorkspaceEntry[]; + recentWorkspaces: RecentWorkspaceEntry[]; + source: WorkspaceCatalogSource; +} + +/** Membership comes from the host; assistant metadata only enriches local rows. */ +export function projectWorkspaceCatalog( + response: RecentWorkspaceListResponse, + assistants: AssistantEntry[] +): WorkspaceCatalog { + const recent = RemoteResponseMapper.recentWorkspaces(response.workspaces || []); + const opened = Array.isArray(response.opened_workspaces); + const rows = opened ? RemoteResponseMapper.recentWorkspaces(response.opened_workspaces || []) : + mergeLegacyWorkspaces(recent, assistants); + const seen: Set = new Set(); + const workspaces: RecentWorkspaceEntry[] = []; + rows.forEach((row: RecentWorkspaceEntry) => { + const key = JSON.stringify([row.remoteConnectionId || null, row.remoteSshHost || null, row.path]); + if (!row.path || seen.has(key)) return; + seen.add(key); + const assistant = !row.remoteConnectionId && !row.remoteSshHost ? + assistants.find((item: AssistantEntry): boolean => item.path === row.path && item.name.trim().length > 0) : undefined; + workspaces.push({ + path: row.path, name: assistant ? assistant.name : row.name, + lastOpened: row.lastOpened, workspaceKind: assistant ? 'assistant' : row.workspaceKind, + remoteConnectionId: row.remoteConnectionId, remoteSshHost: row.remoteSshHost + }); + }); + return { workspaces, recentWorkspaces: recent, source: opened ? 'opened' : 'recent' }; +} + +export function mergeLegacyWorkspaces(recent: RecentWorkspaceEntry[], assistants: AssistantEntry[]): RecentWorkspaceEntry[] { + const rows: RecentWorkspaceEntry[] = assistants.map((item: AssistantEntry): RecentWorkspaceEntry => ({ + path: item.path, name: item.name, lastOpened: '', workspaceKind: 'assistant' + })); + recent.forEach((item: RecentWorkspaceEntry) => { + if (!rows.some((row: RecentWorkspaceEntry): boolean => row.path === item.path && + (row.remoteConnectionId || '') === (item.remoteConnectionId || '') && + (row.remoteSshHost || '') === (item.remoteSshHost || ''))) rows.push(item); + }); + return rows; +} diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceFileUploadClient.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceFileUploadClient.ets index 895a153158..a207ca1a2e 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceFileUploadClient.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/WorkspaceFileUploadClient.ets @@ -19,7 +19,13 @@ export class WorkspaceFileUploadClient { private async call(request: UploadRequest): Promise { if (this.pending && this.pending.targetId !== this.target()) throw new Error('Reconnect to the upload owning runtime first'); const args: UploadArgs = { request: request }; - return this.manager.hostInvoke('workspace_file_upload', args); + const status = await this.manager.hostInvoke('workspace_file_upload', args); + if (request.action !== 'cancel' && (status.transferId !== request.transferId || + status.totalBytes !== request.totalBytes || !Number.isSafeInteger(status.nextOffset) || + status.nextOffset < 0 || status.nextOffset > (request.totalBytes || 0))) { + throw new Error('Invalid upload acknowledgement'); + } + return status; } async select(path: string, remoteConnectionId: string | undefined, expectedHash: string, current: () => boolean, workspacePath: string): Promise { if (this.pending) throw new Error('Finish or cancel the current file upload first'); @@ -58,8 +64,13 @@ export class WorkspaceFileUploadClient { // Begin is idempotent even when its earlier acknowledgement was lost. pending.started = true; request.action = 'begin'; request.contentBase64 = undefined; request.offset = undefined; - let status = await this.call(request); - if (status.transferId !== request.transferId || status.totalBytes !== request.totalBytes || !Number.isSafeInteger(status.nextOffset) || status.nextOffset < 0 || status.nextOffset > (request.totalBytes || 0)) throw new Error('Invalid upload acknowledgement'); + let status: UploadStatus; + try { status = await this.call(request); } + catch (error) { + if (!current() || pending.targetId !== this.target()) throw error as Error; + request.action = 'status'; + status = await this.call(request); + } while (!status.completed && status.nextOffset < (request.totalBytes || 0)) { if (!current() || pending.targetId !== this.target()) return; const offset = status.nextOffset; @@ -79,9 +90,17 @@ export class WorkspaceFileUploadClient { progress(status.nextOffset, request.totalBytes || 0); } if (!current() || pending.targetId !== this.target()) return; - request.action = 'finish'; request.contentBase64 = undefined; - status = await this.call(request); - if (!status.completed) throw new Error('Runtime did not complete the file upload'); + if (!status.completed) { + request.action = 'finish'; request.contentBase64 = undefined; + try { status = await this.call(request); } + catch (error) { + if (!current() || pending.targetId !== this.target()) throw error as Error; + request.action = 'status'; + status = await this.call(request); + if (!status.completed) throw error as Error; + } + } + if (!status.completed || status.nextOffset !== request.totalBytes) throw new Error('Runtime did not complete the file upload'); await fs.close(pending.file); if (this.pending === pending) this.pending = undefined; } async cancel(): Promise { diff --git a/src/apps/mobile/harmonyos/entry/src/main/module.json5 b/src/apps/mobile/harmonyos/entry/src/main/module.json5 index c47e1d3306..98b6f1a2e2 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/module.json5 +++ b/src/apps/mobile/harmonyos/entry/src/main/module.json5 @@ -63,6 +63,7 @@ "abilities": [ { "name": "EntryAbility", + "orientation": "auto_rotation_unspecified", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:openbitfun_app_icon", diff --git a/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json b/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json index 888e8e66f1..5020c4c5b6 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json +++ b/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json @@ -191,6 +191,10 @@ { "name": "welcome_secondary", "value": "#B9BCB9" + }, + { + "name": "status_warning", + "value": "#FF8C00" } ] } diff --git a/src/apps/mobile/harmonyos/entry/src/main/resources/base/media/approval_shield.png b/src/apps/mobile/harmonyos/entry/src/main/resources/base/media/approval_shield.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb2022901ab1225e7d7ed273f97e49423c609 GIT binary patch literal 1324 zcmV+{1=IS8P)g&UE5TM>2u2i3TS2M_VzG7Q0~boG7}AGSORLo*-^|3= zbpDyioquj-Zsx!bfh6BK|C#?e_xmo;ph1Iv24BT_6bw-&JBKkd+jefj#pq{Hv5ty^U6>O!!2t^H zLcf8dxQ2p{x;ExaqdwpWR-iA8QM^gPsT>`jq+)H{CUcl%G2fu#YSamA;Q|Wor(!n+ z|KuDI`zW~bzZe}Xr(%E3|22-C6x?gvs1g`pA&0S)0dA$>NebSl;P@=2%imOV*up?! zY{AytCP!u&_c#?#gqk943wvh>aTXO1$0E94Y@^P6Z=J~GRNPL%4+YmP(>hMU2J1sP z=5%m*c)~5dz#gOG8IIxn+}te;Q1KiUzkA@{Wqm1QP0@&gnH9K7|@D{)BQ;dqyg!rQ#7?gp=pbRtyWuP%A1C2o$Xbj3gV^9VfgZkbeX)dd&XkK^p{R=a}pNS+j z$RUi)PcrDFKN4|#<}{3pP>+l@mia;s;pa=qB+F27SXzxk`%nHz6nh;cYfy1eaGPU_ zec_Kp9Z8pMJc4>;(#VET zitijFXq}JvA*fj7Ooi*JDxJ3?Kv7<5kgsS!O_wPh5nanAYa_M!LM z2;&~ZVk+KE<(}g;F+^CWmHq^+{Yt?@KI$XFx{&}6gfY`OP1Aj-0%#=%*(1rG=x278 z>Y8T+x7b#lgXe^4LY2U1i*?<7W_DnYEv&PQ{c+CN6ZZG0A}Cl!#h1A{5_RTt26!;3 zl+xFa zk>8|ln-%}wj5>nv;2bI*qv99u#zgDg;9Qhmc?Ye6**a6CLh$q>!^Wa7=_(0E#kr4D z@naVw#>{=HFO8WAws3ZMf3j~*NH5T}y84|Aa8Dib07^l4Ww0+Lh`v2xBHq0|4c~ze iV`(|84H`5+HSjM~GsYa4Z9J&}0000{for(var i in t)$r(e,i,{get:t[i],enumerable:!0})},K=(e,t,i,s)=>{for(var r=s>1?void 0:s?Qn(t,i):t,n=e.length-1,o;n>=0;n--)(o=e[n])&&(r=(s?o(t,i,r):o(r))||r);return s&&r&&$r(t,i,r),r},w=(e,t)=>(i,s)=>t(i,s,e),Is="Terminal input",Ti={get:()=>Is,set:e=>Is=e},Ns="Too much output to announce, navigate to rows manually to read",Zt={get:()=>Ns,set:e=>Ns=e};function to(e){return e.replace(/\r?\n/g,"\r")}function io(e,t){return t?"\x1B[200~"+e+"\x1B[201~":e}function so(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()}function ro(e,t,i,s){if(e.stopPropagation(),e.clipboardData){let r=e.clipboardData.getData("text/plain");Ur(r,t,i,s)}}function Ur(e,t,i,s){e=to(e),e=io(e,i.decPrivateModes.bracketedPasteMode&&s.rawOptions.ignoreBracketedPasteMode!==!0),i.triggerDataEvent(e,!0),t.value=""}function qr(e,t,i){let s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}function Fs(e,t,i,s,r){qr(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}function Ve(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function at(e,t=0,i=e.length){let s="";for(let r=t;r65535?(n-=65536,s+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):s+=String.fromCharCode(n)}return s}var no=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){let n=e.charCodeAt(r++);56320<=n&&n<=57343?t[s++]=(this._interim-55296)*1024+n-56320+65536:(t[s++]=this._interim,t[s++]=n),this._interim=0}for(let n=r;n=i)return this._interim=o,s;let h=e.charCodeAt(n);56320<=h&&h<=57343?t[s++]=(o-55296)*1024+h-56320+65536:(t[s++]=o,t[s++]=h);continue}o!==65279&&(t[s++]=o)}return s}},oo=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r,n,o,h,l=0,a=0;if(this.interim[0]){let u=!1,f=this.interim[0];f&=(f&224)===192?31:(f&240)===224?15:7;let _=0,p;for(;(p=this.interim[++_]&63)&&_<4;)f<<=6,f|=p;let y=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,E=y-_;for(;a=i)return 0;if(p=e[a++],(p&192)!==128){a--,u=!0;break}else this.interim[_++]=p,f<<=6,f|=p&63}u||(y===2?f<128?a--:t[s++]=f:y===3?f<2048||f>=55296&&f<=57343||f===65279||(t[s++]=f):f<65536||f>1114111||(t[s++]=f)),this.interim.fill(0)}let d=i-4,c=a;for(;c=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(l=(r&31)<<6|n&63,l<128){c--;continue}t[s++]=l}else if((r&240)===224){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(l=(r&15)<<12|(n&63)<<6|o&63,l<2048||l>=55296&&l<=57343||l===65279)continue;t[s++]=l}else if((r&248)===240){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=o,s;if(h=e[c++],(h&192)!==128){c--;continue}if(l=(r&7)<<18|(n&63)<<12|(o&63)<<6|h&63,l<65536||l>1114111)continue;t[s++]=l}}return s}},Pt=class Vr{constructor(){this.fg=0,this.bg=0,this.extended=new Qt}static toColorRGB(t){return[t>>>16&255,t>>>8&255,t&255]}static fromColorRGB(t){return(t[0]&255)<<16|(t[1]&255)<<8|t[2]&255}clone(){let t=new Vr;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},Qt=class Yr{constructor(t=0,i=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=i}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(t){this._ext&=-67108864,this._ext|=t&67108863}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){let t=(this._ext&3758096384)>>29;return t<0?t^4294967288:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}clone(){return new Yr(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},ke=class jr extends Pt{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new Qt,this.combinedData=""}static fromCharData(t){let i=new jr;return i.setFromCharData(t),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?Ve(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let i=!1;if(t[1].length>2)i=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let r=t[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(s-55296)*1024+r-56320+65536|t[2]<<22:i=!0}else i=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;i&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},Hs="di$target",Ai="di$dependencies",pi=new Map;function ao(e){return e[Ai]||[]}function G(e){if(pi.has(e))return pi.get(e);let t=function(i,s,r){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");ho(t,i,r)};return t._id=e,pi.set(e,t),t}function ho(e,t,i){t[Hs]===t?t[Ai].push({id:e,index:i}):(t[Ai]=[{id:e,index:i}],t[Hs]=t)}var ce=G("BufferService"),Xr=G("CoreMouseService"),je=G("CoreService"),lo=G("CharsetService"),Ss=G("InstantiationService"),Gr=G("LogService"),he=G("OptionsService"),Jr=G("OscLinkService"),co=G("UnicodeService"),Tt=G("DecorationService"),Oi=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new ke}provideLinks(e,t){let i=this._bufferService.buffer.lines.get(e-1);if(!i){t(void 0);return}let s=[],r=this._optionsService.rawOptions.linkHandler,n=this._workCell,o=i.getTrimmedLength(),h=-1,l=-1,a=!1;for(let d=0;dr?r.activate(_,p,u):uo(_,p),hover:(_,p)=>r?.hover?.(_,p,u),leave:(_,p)=>r?.leave?.(_,p,u)})}a=!1,n.hasExtendedAttrs()&&n.extended.urlId?(l=d,h=n.extended.urlId):(l=-1,h=-1)}}t(s)}};Oi=K([w(0,ce),w(1,he),w(2,Jr)],Oi);function uo(e,t){if(confirm(`Do you want to navigate to ${t}? + */var qr=Object.defineProperty,io=Object.getOwnPropertyDescriptor,so=(e,t)=>{for(var i in t)qr(e,i,{get:t[i],enumerable:!0})},K=(e,t,i,s)=>{for(var r=s>1?void 0:s?io(t,i):t,n=e.length-1,o;n>=0;n--)(o=e[n])&&(r=(s?o(t,i,r):o(r))||r);return s&&r&&qr(t,i,r),r},w=(e,t)=>(i,s)=>t(i,s,e),Fs="Terminal input",Oi={get:()=>Fs,set:e=>Fs=e},Hs="Too much output to announce, navigate to rows manually to read",ei={get:()=>Hs,set:e=>Hs=e};function ro(e){return e.replace(/\r?\n/g,"\r")}function no(e,t){return t?"\x1B[200~"+e+"\x1B[201~":e}function oo(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()}function ao(e,t,i,s){if(e.stopPropagation(),e.clipboardData){let r=e.clipboardData.getData("text/plain");Vr(r,t,i,s)}}function Vr(e,t,i,s){e=ro(e),e=no(e,i.decPrivateModes.bracketedPasteMode&&s.rawOptions.ignoreBracketedPasteMode!==!0),i.triggerDataEvent(e,!0),t.value=""}function Yr(e,t,i){let s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}function Ws(e,t,i,s,r){Yr(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}function Ye(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function ht(e,t=0,i=e.length){let s="";for(let r=t;r65535?(n-=65536,s+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):s+=String.fromCharCode(n)}return s}var ho=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){let n=e.charCodeAt(r++);56320<=n&&n<=57343?t[s++]=(this._interim-55296)*1024+n-56320+65536:(t[s++]=this._interim,t[s++]=n),this._interim=0}for(let n=r;n=i)return this._interim=o,s;let h=e.charCodeAt(n);56320<=h&&h<=57343?t[s++]=(o-55296)*1024+h-56320+65536:(t[s++]=o,t[s++]=h);continue}o!==65279&&(t[s++]=o)}return s}},lo=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r,n,o,h,l=0,a=0;if(this.interim[0]){let u=!1,f=this.interim[0];f&=(f&224)===192?31:(f&240)===224?15:7;let _=0,p;for(;(p=this.interim[++_]&63)&&_<4;)f<<=6,f|=p;let y=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,E=y-_;for(;a=i)return 0;if(p=e[a++],(p&192)!==128){a--,u=!0;break}else this.interim[_++]=p,f<<=6,f|=p&63}u||(y===2?f<128?a--:t[s++]=f:y===3?f<2048||f>=55296&&f<=57343||f===65279||(t[s++]=f):f<65536||f>1114111||(t[s++]=f)),this.interim.fill(0)}let d=i-4,c=a;for(;c=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(l=(r&31)<<6|n&63,l<128){c--;continue}t[s++]=l}else if((r&240)===224){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(l=(r&15)<<12|(n&63)<<6|o&63,l<2048||l>=55296&&l<=57343||l===65279)continue;t[s++]=l}else if((r&248)===240){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=o,s;if(h=e[c++],(h&192)!==128){c--;continue}if(l=(r&7)<<18|(n&63)<<12|(o&63)<<6|h&63,l<65536||l>1114111)continue;t[s++]=l}}return s}},Tt=class jr{constructor(){this.fg=0,this.bg=0,this.extended=new ti}static toColorRGB(t){return[t>>>16&255,t>>>8&255,t&255]}static fromColorRGB(t){return(t[0]&255)<<16|(t[1]&255)<<8|t[2]&255}clone(){let t=new jr;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},ti=class Xr{constructor(t=0,i=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=i}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(t){this._ext&=-67108864,this._ext|=t&67108863}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){let t=(this._ext&3758096384)>>29;return t<0?t^4294967288:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}clone(){return new Xr(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},ke=class Gr extends Tt{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new ti,this.combinedData=""}static fromCharData(t){let i=new Gr;return i.setFromCharData(t),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?Ye(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let i=!1;if(t[1].length>2)i=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let r=t[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(s-55296)*1024+r-56320+65536|t[2]<<22:i=!0}else i=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;i&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},zs="di$target",Ii="di$dependencies",vi=new Map;function co(e){return e[Ii]||[]}function G(e){if(vi.has(e))return vi.get(e);let t=function(i,s,r){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");uo(t,i,r)};return t._id=e,vi.set(e,t),t}function uo(e,t,i){t[zs]===t?t[Ii].push({id:e,index:i}):(t[Ii]=[{id:e,index:i}],t[zs]=t)}var ce=G("BufferService"),Jr=G("CoreMouseService"),Xe=G("CoreService"),_o=G("CharsetService"),bs=G("InstantiationService"),Zr=G("LogService"),he=G("OptionsService"),Qr=G("OscLinkService"),fo=G("UnicodeService"),At=G("DecorationService"),Ni=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new ke}provideLinks(e,t){let i=this._bufferService.buffer.lines.get(e-1);if(!i){t(void 0);return}let s=[],r=this._optionsService.rawOptions.linkHandler,n=this._workCell,o=i.getTrimmedLength(),h=-1,l=-1,a=!1;for(let d=0;dr?r.activate(_,p,u):po(_,p),hover:(_,p)=>r?.hover?.(_,p,u),leave:(_,p)=>r?.leave?.(_,p,u)})}a=!1,n.hasExtendedAttrs()&&n.extended.urlId?(l=d,h=n.extended.urlId):(l=-1,h=-1)}}t(s)}};Ni=K([w(0,ce),w(1,he),w(2,Qr)],Ni);function po(e,t){if(confirm(`Do you want to navigate to ${t}? -WARNING: This link could potentially be dangerous`)){let i=window.open();if(i){try{i.opener=null}catch{}i.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}var hi=G("CharSizeService"),Ie=G("CoreBrowserService"),ws=G("MouseService"),Ne=G("RenderService"),_o=G("SelectionService"),Zr=G("CharacterJoinerService"),ht=G("ThemeService"),Qr=G("LinkProviderService"),fo=G("KeyboardService");function q(e){return{dispose:e}}function Ii(e){if(e)if(Array.isArray(e))for(let t of e)t.dispose();else e.dispose()}var bs=class{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(let e of this._disposables)e.dispose();this._disposables.clear()}}},I=class{constructor(){this._store=new bs}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}},ot=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}},me=typeof window=="object"?window:globalThis,po=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?Ws.isErrorNoTelemetry(e)?new Ws(e.message+` +WARNING: This link could potentially be dangerous`)){let i=window.open();if(i){try{i.opener=null}catch{}i.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}var ci=G("CharSizeService"),Ne=G("CoreBrowserService"),ys=G("MouseService"),Fe=G("RenderService"),go=G("SelectionService"),en=G("CharacterJoinerService"),lt=G("ThemeService"),tn=G("LinkProviderService"),vo=G("KeyboardService");function q(e){return{dispose:e}}function Fi(e){if(e)if(Array.isArray(e))for(let t of e)t.dispose();else e.dispose()}var Cs=class{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(let e of this._disposables)e.dispose();this._disposables.clear()}}},I=class{constructor(){this._store=new Cs}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}},at=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}},me=typeof window=="object"?window:globalThis,mo=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?Ks.isErrorNoTelemetry(e)?new Ks(e.message+` `+e.stack):new Error(e.message+` -`+e.stack):e},0)}}addListener(e){return this.listeners.push(e),()=>{this._removeListener(e)}}emit(e){this.listeners.forEach(t=>{t(e)})}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}},go=new po;function Kt(e){vo(e)||go.onUnexpectedError(e)}var Ni="Canceled";function vo(e){return e instanceof mo?!0:e instanceof Error&&e.name===Ni&&e.message===Ni}var mo=class extends Error{constructor(){super(Ni),this.name=this.message}};function So(e){return e?new Error(`Illegal argument: ${e}`):new Error("Illegal argument")}var Ws=class Fi extends Error{constructor(t){super(t),this.name="CodeExpectedError"}static fromError(t){if(t instanceof Fi)return t;let i=new Fi;return i.message=t.message,i.stack=t.stack,i}static isErrorNoTelemetry(t){return t.name==="CodeExpectedError"}},Hi=class en extends Error{constructor(t){super(t||"An unexpected bug occurred."),Object.setPrototypeOf(this,en.prototype)}};function wo(e,t){let i=this,s=!1,r;return function(){if(s)return r;if(s=!0,t)try{r=e.apply(i,arguments)}finally{t()}else r=e.apply(i,arguments);return r}}function bo(e,t,i=0,s=e.length){let r=i,n=s;for(;r{function t(n){return n<0}e.isLessThan=t;function i(n){return n<=0}e.isLessThanOrEqual=i;function s(n){return n>0}e.isGreaterThan=s;function r(n){return n===0}e.isNeitherLessOrGreaterThan=r,e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0})(sn||(sn={}));function Co(e,t){return(i,s)=>t(e(i),e(s))}var ko=(e,t)=>e-t,zs=class Wi{constructor(t){this.iterate=t}forEach(t){this.iterate(i=>(t(i),!0))}toArray(){let t=[];return this.iterate(i=>(t.push(i),!0)),t}filter(t){return new Wi(i=>this.iterate(s=>t(s)?i(s):!0))}map(t){return new Wi(i=>this.iterate(s=>i(t(s))))}some(t){let i=!1;return this.iterate(s=>(i=t(s),!i)),i}findFirst(t){let i;return this.iterate(s=>t(s)?(i=s,!1):!0),i}findLast(t){let i;return this.iterate(s=>(t(s)&&(i=s),!0)),i}findLastMaxBy(t){let i,s=!0;return this.iterate(r=>((s||sn.isGreaterThan(t(r,i)))&&(s=!1,i=r),!0)),i}};zs.empty=new zs(e=>{});function xo(e,t){let i=Object.create(null);for(let s of e){let r=t(s),n=i[r];n||(n=i[r]=[]),n.push(s)}return i}var Ks,$s,zl=class{constructor(e,t){this.toKey=t,this._map=new Map,this[Ks]="SetWithKey";for(let i of e)this.add(i)}get size(){return this._map.size}add(e){let t=this.toKey(e);return this._map.set(t,e),this}delete(e){return this._map.delete(this.toKey(e))}has(e){return this._map.has(this.toKey(e))}*entries(){for(let e of this._map.values())yield[e,e]}keys(){return this.values()}*values(){for(let e of this._map.values())yield e}clear(){this._map.clear()}forEach(e,t){this._map.forEach(i=>e.call(t,i,i,this))}[($s=Symbol.iterator,Ks=Symbol.toStringTag,$s)](){return this.values()}},Eo=class{constructor(){this.map=new Map}add(e,t){let i=this.map.get(e);i||(i=new Set,this.map.set(e,i)),i.add(t)}delete(e,t){let i=this.map.get(e);i&&(i.delete(t),i.size===0&&this.map.delete(e))}forEach(e,t){let i=this.map.get(e);i&&i.forEach(t)}get(e){return this.map.get(e)||new Set}},rn;(e=>{function t(x){return x&&typeof x=="object"&&typeof x[Symbol.iterator]=="function"}e.is=t;let i=Object.freeze([]);function s(){return i}e.empty=s;function*r(x){yield x}e.single=r;function n(x){return t(x)?x:r(x)}e.wrap=n;function o(x){return x||i}e.from=o;function*h(x){for(let L=x.length-1;L>=0;L--)yield x[L]}e.reverse=h;function l(x){return!x||x[Symbol.iterator]().next().done===!0}e.isEmpty=l;function a(x){return x[Symbol.iterator]().next().value}e.first=a;function d(x,L){let P=0;for(let re of x)if(L(re,P++))return!0;return!1}e.some=d;function c(x,L){for(let P of x)if(L(P))return P}e.find=c;function*u(x,L){for(let P of x)L(P)&&(yield P)}e.filter=u;function*f(x,L){let P=0;for(let re of x)yield L(re,P++)}e.map=f;function*_(x,L){let P=0;for(let re of x)yield*L(re,P++)}e.flatMap=_;function*p(...x){for(let L of x)yield*L}e.concat=p;function y(x,L,P){let re=P;for(let ye of x)re=L(re,ye);return re}e.reduce=y;function*E(x,L,P=x.length){for(L<0&&(L+=x.length),P<0?P+=x.length:P>x.length&&(P=x.length);Li.source!==null&&!this.getRootParent(i,t).isSingleton).flatMap(([i])=>i)}computeLeakingDisposables(t=10,i){let s;if(i)s=i;else{let l=new Map,a=[...this.livingDisposables.values()].filter(c=>c.source!==null&&!this.getRootParent(c,l).isSingleton);if(a.length===0)return;let d=new Set(a.map(c=>c.value));if(s=a.filter(c=>!(c.parent&&d.has(c.parent))),s.length===0)throw new Error("There are cyclic diposable chains!")}if(!s)return;function r(l){function a(c,u){for(;c.length>0&&u.some(f=>typeof f=="string"?f===c[0]:c[0].match(f));)c.shift()}let d=l.source.split(` -`).map(c=>c.trim().replace("at ","")).filter(c=>c!=="");return a(d,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),d.reverse()}let n=new Eo;for(let l of s){let a=r(l);for(let d=0;d<=a.length;d++)n.add(a.slice(0,d).join(` -`),l)}s.sort(Co(l=>l.idx,ko));let o="",h=0;for(let l of s.slice(0,t)){h++;let a=r(l),d=[];for(let c=0;c{this._removeListener(e)}}emit(e){this.listeners.forEach(t=>{t(e)})}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}},So=new mo;function Ut(e){wo(e)||So.onUnexpectedError(e)}var Hi="Canceled";function wo(e){return e instanceof bo?!0:e instanceof Error&&e.name===Hi&&e.message===Hi}var bo=class extends Error{constructor(){super(Hi),this.name=this.message}};function yo(e){return e?new Error(`Illegal argument: ${e}`):new Error("Illegal argument")}var Ks=class Wi extends Error{constructor(t){super(t),this.name="CodeExpectedError"}static fromError(t){if(t instanceof Wi)return t;let i=new Wi;return i.message=t.message,i.stack=t.stack,i}static isErrorNoTelemetry(t){return t.name==="CodeExpectedError"}},zi=class sn extends Error{constructor(t){super(t||"An unexpected bug occurred."),Object.setPrototypeOf(this,sn.prototype)}};function Co(e,t){let i=this,s=!1,r;return function(){if(s)return r;if(s=!0,t)try{r=e.apply(i,arguments)}finally{t()}else r=e.apply(i,arguments);return r}}function ko(e,t,i=0,s=e.length){let r=i,n=s;for(;r{function t(n){return n<0}e.isLessThan=t;function i(n){return n<=0}e.isLessThanOrEqual=i;function s(n){return n>0}e.isGreaterThan=s;function r(n){return n===0}e.isNeitherLessOrGreaterThan=r,e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0})(nn||(nn={}));function Eo(e,t){return(i,s)=>t(e(i),e(s))}var Bo=(e,t)=>e-t,$s=class Ki{constructor(t){this.iterate=t}forEach(t){this.iterate(i=>(t(i),!0))}toArray(){let t=[];return this.iterate(i=>(t.push(i),!0)),t}filter(t){return new Ki(i=>this.iterate(s=>t(s)?i(s):!0))}map(t){return new Ki(i=>this.iterate(s=>i(t(s))))}some(t){let i=!1;return this.iterate(s=>(i=t(s),!i)),i}findFirst(t){let i;return this.iterate(s=>t(s)?(i=s,!1):!0),i}findLast(t){let i;return this.iterate(s=>(t(s)&&(i=s),!0)),i}findLastMaxBy(t){let i,s=!0;return this.iterate(r=>((s||nn.isGreaterThan(t(r,i)))&&(s=!1,i=r),!0)),i}};$s.empty=new $s(e=>{});function Do(e,t){let i=Object.create(null);for(let s of e){let r=t(s),n=i[r];n||(n=i[r]=[]),n.push(s)}return i}var Us,qs,Ul=class{constructor(e,t){this.toKey=t,this._map=new Map,this[Us]="SetWithKey";for(let i of e)this.add(i)}get size(){return this._map.size}add(e){let t=this.toKey(e);return this._map.set(t,e),this}delete(e){return this._map.delete(this.toKey(e))}has(e){return this._map.has(this.toKey(e))}*entries(){for(let e of this._map.values())yield[e,e]}keys(){return this.values()}*values(){for(let e of this._map.values())yield e}clear(){this._map.clear()}forEach(e,t){this._map.forEach(i=>e.call(t,i,i,this))}[(qs=Symbol.iterator,Us=Symbol.toStringTag,qs)](){return this.values()}},Ro=class{constructor(){this.map=new Map}add(e,t){let i=this.map.get(e);i||(i=new Set,this.map.set(e,i)),i.add(t)}delete(e,t){let i=this.map.get(e);i&&(i.delete(t),i.size===0&&this.map.delete(e))}forEach(e,t){let i=this.map.get(e);i&&i.forEach(t)}get(e){return this.map.get(e)||new Set}},on;(e=>{function t(x){return x&&typeof x=="object"&&typeof x[Symbol.iterator]=="function"}e.is=t;let i=Object.freeze([]);function s(){return i}e.empty=s;function*r(x){yield x}e.single=r;function n(x){return t(x)?x:r(x)}e.wrap=n;function o(x){return x||i}e.from=o;function*h(x){for(let L=x.length-1;L>=0;L--)yield x[L]}e.reverse=h;function l(x){return!x||x[Symbol.iterator]().next().done===!0}e.isEmpty=l;function a(x){return x[Symbol.iterator]().next().value}e.first=a;function d(x,L){let P=0;for(let re of x)if(L(re,P++))return!0;return!1}e.some=d;function c(x,L){for(let P of x)if(L(P))return P}e.find=c;function*u(x,L){for(let P of x)L(P)&&(yield P)}e.filter=u;function*f(x,L){let P=0;for(let re of x)yield L(re,P++)}e.map=f;function*_(x,L){let P=0;for(let re of x)yield*L(re,P++)}e.flatMap=_;function*p(...x){for(let L of x)yield*L}e.concat=p;function y(x,L,P){let re=P;for(let ye of x)re=L(re,ye);return re}e.reduce=y;function*E(x,L,P=x.length){for(L<0&&(L+=x.length),P<0?P+=x.length:P>x.length&&(P=x.length);Li.source!==null&&!this.getRootParent(i,t).isSingleton).flatMap(([i])=>i)}computeLeakingDisposables(t=10,i){let s;if(i)s=i;else{let l=new Map,a=[...this.livingDisposables.values()].filter(c=>c.source!==null&&!this.getRootParent(c,l).isSingleton);if(a.length===0)return;let d=new Set(a.map(c=>c.value));if(s=a.filter(c=>!(c.parent&&d.has(c.parent))),s.length===0)throw new Error("There are cyclic diposable chains!")}if(!s)return;function r(l){function a(c,u){for(;c.length>0&&u.some(f=>typeof f=="string"?f===c[0]:c[0].match(f));)c.shift()}let d=l.source.split(` +`).map(c=>c.trim().replace("at ","")).filter(c=>c!=="");return a(d,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),d.reverse()}let n=new Ro;for(let l of s){let a=r(l);for(let d=0;d<=a.length;d++)n.add(a.slice(0,d).join(` +`),l)}s.sort(Eo(l=>l.idx,Bo));let o="",h=0;for(let l of s.slice(0,t)){h++;let a=r(l),d=[];for(let c=0;cr(p)[c]),p=>p);delete _[a[c]];for(let[p,y]of Object.entries(_))d.unshift(` - stacktraces of ${y.length} other leaks continue with ${p}`);d.unshift(u)}o+=` +`)),_=Do([...f].map(p=>r(p)[c]),p=>p);delete _[a[c]];for(let[p,y]of Object.entries(_))d.unshift(` - stacktraces of ${y.length} other leaks continue with ${p}`);d.unshift(u)}o+=` ==================== Leaking disposable ${h}/${s.length}: ${l.value.constructor.name} ==================== @@ -32,19 +32,19 @@ ${d.join(` ... and ${s.length-t} more leaking disposables -`),{leaks:s,details:o}}};Do.idx=0;function Ro(e){tt=e}if(Bo){let e="__is_disposable_tracked__";Ro(new class{trackDisposable(t){let i=new Error("Potentially leaked disposable").stack;setTimeout(()=>{t[e]||console.log(i)},3e3)}setParent(t,i){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsDisposed(t){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsSingleton(t){}})}function ys(e){return tt?.trackDisposable(e),e}function Cs(e){tt?.markAsDisposed(e)}function zi(e,t){tt?.setParent(e,t)}function Mo(e,t){if(tt)for(let i of e)tt.setParent(i,t)}function Us(e){return tt?.markAsSingleton(e),e}function ei(e){if(rn.is(e)){let t=[];for(let i of e)if(i)try{i.dispose()}catch(s){t.push(s)}if(t.length===1)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(e)?[]:e}else if(e)return e.dispose(),e}function Lo(...e){let t=Ye(()=>ei(e));return Mo(e,t),t}function Ye(e){let t=ys({dispose:wo(()=>{Cs(t),e()})});return t}var on=class an{constructor(){this._toDispose=new Set,this._isDisposed=!1,ys(this)}dispose(){this._isDisposed||(Cs(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{ei(this._toDispose)}finally{this._toDispose.clear()}}add(t){if(!t)return t;if(t===this)throw new Error("Cannot register a disposable on itself!");return zi(t,this),this._isDisposed?an.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(t),t}delete(t){if(t){if(t===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(t),t.dispose()}}deleteAndLeak(t){t&&this._toDispose.has(t)&&(this._toDispose.delete(t),zi(t,null))}};on.DISABLE_DISPOSED_WARNING=!1;var et=on,Se=class{constructor(){this._store=new et,ys(this),zi(this._store,this)}dispose(){Cs(this),this._store.dispose()}_register(e){if(e===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(e)}};Se.None=Object.freeze({dispose(){}});var Ki=class $i{constructor(t){this.element=t,this.next=$i.Undefined,this.prev=$i.Undefined}};Ki.Undefined=new Ki(void 0);var W=Ki,qs=class{constructor(){this._first=W.Undefined,this._last=W.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===W.Undefined}clear(){let e=this._first;for(;e!==W.Undefined;){let t=e.next;e.prev=W.Undefined,e.next=W.Undefined,e=t}this._first=W.Undefined,this._last=W.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){let i=new W(e);if(this._first===W.Undefined)this._first=i,this._last=i;else if(t){let r=this._last;this._last=i,i.prev=r,r.next=i}else{let r=this._first;this._first=i,i.next=r,r.prev=i}this._size+=1;let s=!1;return()=>{s||(s=!0,this._remove(i))}}shift(){if(this._first!==W.Undefined){let e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==W.Undefined){let e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==W.Undefined&&e.next!==W.Undefined){let t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===W.Undefined&&e.next===W.Undefined?(this._first=W.Undefined,this._last=W.Undefined):e.next===W.Undefined?(this._last=this._last.prev,this._last.next=W.Undefined):e.prev===W.Undefined&&(this._first=this._first.next,this._first.prev=W.Undefined);this._size-=1}*[Symbol.iterator](){let e=this._first;for(;e!==W.Undefined;)yield e.element,e=e.next}},Po=globalThis.performance&&typeof globalThis.performance.now=="function",To=class hn{static create(t){return new hn(t)}constructor(t){this._now=Po&&t===!1?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return this._stopTime!==-1?this._stopTime-this._startTime:this._now()-this._startTime}},Ao=!1,Vs=!1,Oo=!1,li;(e=>{e.None=()=>Se.None;function t(m){if(Oo){let{onDidAddListener:g}=m,C=Vi.create(),S=0;m.onDidAddListener=()=>{++S===2&&(console.warn("snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here"),C.print()),g?.()}}}function i(m,g){return u(m,()=>{},0,void 0,!0,void 0,g)}e.defer=i;function s(m){return(g,C=null,S)=>{let k=!1,v;return v=m(M=>{if(!k)return v?v.dispose():k=!0,g.call(C,M)},null,S),k&&v.dispose(),v}}e.once=s;function r(m,g,C){return d((S,k=null,v)=>m(M=>S.call(k,g(M)),null,v),C)}e.map=r;function n(m,g,C){return d((S,k=null,v)=>m(M=>{g(M),S.call(k,M)},null,v),C)}e.forEach=n;function o(m,g,C){return d((S,k=null,v)=>m(M=>g(M)&&S.call(k,M),null,v),C)}e.filter=o;function h(m){return m}e.signal=h;function l(...m){return(g,C=null,S)=>{let k=Lo(...m.map(v=>v(M=>g.call(C,M))));return c(k,S)}}e.any=l;function a(m,g,C,S){let k=C;return r(m,v=>(k=g(k,v),k),S)}e.reduce=a;function d(m,g){let C,S={onWillAddFirstListener(){C=m(k.fire,k)},onDidRemoveLastListener(){C?.dispose()}};g||t(S);let k=new se(S);return g?.add(k),k.event}function c(m,g){return g instanceof Array?g.push(m):g&&g.add(m),m}function u(m,g,C=100,S=!1,k=!1,v,M){let H,ne,Ee,Le=0,J,He={leakWarningThreshold:v,onWillAddFirstListener(){H=m(Xe=>{Le++,ne=g(ne,Xe),S&&!Ee&&(le.fire(ne),ne=void 0),J=()=>{let dt=ne;ne=void 0,Ee=void 0,(!S||Le>1)&&le.fire(dt),Le=0},typeof C=="number"?(clearTimeout(Ee),Ee=setTimeout(J,C)):Ee===void 0&&(Ee=0,queueMicrotask(J))})},onWillRemoveListener(){k&&Le>0&&J?.()},onDidRemoveLastListener(){J=void 0,H.dispose()}};M||t(He);let le=new se(He);return M?.add(le),le.event}e.debounce=u;function f(m,g=0,C){return e.debounce(m,(S,k)=>S?(S.push(k),S):[k],g,void 0,!0,void 0,C)}e.accumulate=f;function _(m,g=(S,k)=>S===k,C){let S=!0,k;return o(m,v=>{let M=S||!g(v,k);return S=!1,k=v,M},C)}e.latch=_;function p(m,g,C){return[e.filter(m,g,C),e.filter(m,S=>!g(S),C)]}e.split=p;function y(m,g=!1,C=[],S){let k=C.slice(),v=m(ne=>{k?k.push(ne):H.fire(ne)});S&&S.add(v);let M=()=>{k?.forEach(ne=>H.fire(ne)),k=null},H=new se({onWillAddFirstListener(){v||(v=m(ne=>H.fire(ne)),S&&S.add(v))},onDidAddFirstListener(){k&&(g?setTimeout(M):M())},onDidRemoveLastListener(){v&&v.dispose(),v=null}});return S&&S.add(H),H.event}e.buffer=y;function E(m,g){return(C,S,k)=>{let v=g(new R);return m(function(M){let H=v.evaluate(M);H!==A&&C.call(S,H)},void 0,k)}}e.chain=E;let A=Symbol("HaltChainable");class R{constructor(){this.steps=[]}map(g){return this.steps.push(g),this}forEach(g){return this.steps.push(C=>(g(C),C)),this}filter(g){return this.steps.push(C=>g(C)?C:A),this}reduce(g,C){let S=C;return this.steps.push(k=>(S=g(S,k),S)),this}latch(g=(C,S)=>C===S){let C=!0,S;return this.steps.push(k=>{let v=C||!g(k,S);return C=!1,S=k,v?k:A}),this}evaluate(g){for(let C of this.steps)if(g=C(g),g===A)break;return g}}function x(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.on(g,S),v=()=>m.removeListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromNodeEventEmitter=x;function L(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.addEventListener(g,S),v=()=>m.removeEventListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromDOMEventEmitter=L;function P(m){return new Promise(g=>s(m)(g))}e.toPromise=P;function re(m){let g=new se;return m.then(C=>{g.fire(C)},()=>{g.fire(void 0)}).finally(()=>{g.dispose()}),g.event}e.fromPromise=re;function ye(m,g){return m(C=>g.fire(C))}e.forward=ye;function xe(m,g,C){return g(C),m(S=>g(S))}e.runAndSubscribe=xe;class Ot{constructor(g,C){this._observable=g,this._counter=0,this._hasChanged=!1;let S={onWillAddFirstListener:()=>{g.addObserver(this)},onDidRemoveLastListener:()=>{g.removeObserver(this)}};C||t(S),this.emitter=new se(S),C&&C.add(this.emitter)}beginUpdate(g){this._counter++}handlePossibleChange(g){}handleChange(g,C){this._hasChanged=!0}endUpdate(g){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function F(m,g){return new Ot(m,g).emitter.event}e.fromObservable=F;function _i(m){return(g,C,S)=>{let k=0,v=!1,M={beginUpdate(){k++},endUpdate(){k--,k===0&&(m.reportChanges(),v&&(v=!1,g.call(C)))},handlePossibleChange(){},handleChange(){v=!0}};m.addObserver(M),m.reportChanges();let H={dispose(){m.removeObserver(M)}};return S instanceof et?S.add(H):Array.isArray(S)&&S.push(H),H}}e.fromObservableLight=_i})(li||(li={}));var Ui=class qi{constructor(t){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${t}_${qi._idPool++}`,qi.all.add(this)}start(t){this._stopWatch=new To,this.listenerCount=t}stop(){if(this._stopWatch){let t=this._stopWatch.elapsed();this.durations.push(t),this.elapsedOverall+=t,this.invocationCount+=1,this._stopWatch=void 0}}};Ui.all=new Set,Ui._idPool=0;var Io=Ui,Ys=-1,ln=class cn{constructor(t,i,s=(cn._idPool++).toString(16).padStart(3,"0")){this._errorHandler=t,this.threshold=i,this.name=s,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(t,i){let s=this.threshold;if(s<=0||i{let n=this._stacks.get(t.value)||0;this._stacks.set(t.value,n-1)}}getMostFrequentStack(){if(!this._stacks)return;let t,i=0;for(let[s,r]of this._stacks)(!t||i{t[e]||console.log(i)},3e3)}setParent(t,i){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsDisposed(t){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsSingleton(t){}})}function ks(e){return it?.trackDisposable(e),e}function xs(e){it?.markAsDisposed(e)}function $i(e,t){it?.setParent(e,t)}function To(e,t){if(it)for(let i of e)it.setParent(i,t)}function Vs(e){return it?.markAsSingleton(e),e}function ii(e){if(on.is(e)){let t=[];for(let i of e)if(i)try{i.dispose()}catch(s){t.push(s)}if(t.length===1)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(e)?[]:e}else if(e)return e.dispose(),e}function Ao(...e){let t=je(()=>ii(e));return To(e,t),t}function je(e){let t=ks({dispose:Co(()=>{xs(t),e()})});return t}var hn=class ln{constructor(){this._toDispose=new Set,this._isDisposed=!1,ks(this)}dispose(){this._isDisposed||(xs(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{ii(this._toDispose)}finally{this._toDispose.clear()}}add(t){if(!t)return t;if(t===this)throw new Error("Cannot register a disposable on itself!");return $i(t,this),this._isDisposed?ln.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(t),t}delete(t){if(t){if(t===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(t),t.dispose()}}deleteAndLeak(t){t&&this._toDispose.has(t)&&(this._toDispose.delete(t),$i(t,null))}};hn.DISABLE_DISPOSED_WARNING=!1;var tt=hn,Se=class{constructor(){this._store=new tt,ks(this),$i(this._store,this)}dispose(){xs(this),this._store.dispose()}_register(e){if(e===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(e)}};Se.None=Object.freeze({dispose(){}});var Ui=class qi{constructor(t){this.element=t,this.next=qi.Undefined,this.prev=qi.Undefined}};Ui.Undefined=new Ui(void 0);var W=Ui,Ys=class{constructor(){this._first=W.Undefined,this._last=W.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===W.Undefined}clear(){let e=this._first;for(;e!==W.Undefined;){let t=e.next;e.prev=W.Undefined,e.next=W.Undefined,e=t}this._first=W.Undefined,this._last=W.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){let i=new W(e);if(this._first===W.Undefined)this._first=i,this._last=i;else if(t){let r=this._last;this._last=i,i.prev=r,r.next=i}else{let r=this._first;this._first=i,i.next=r,r.prev=i}this._size+=1;let s=!1;return()=>{s||(s=!0,this._remove(i))}}shift(){if(this._first!==W.Undefined){let e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==W.Undefined){let e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==W.Undefined&&e.next!==W.Undefined){let t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===W.Undefined&&e.next===W.Undefined?(this._first=W.Undefined,this._last=W.Undefined):e.next===W.Undefined?(this._last=this._last.prev,this._last.next=W.Undefined):e.prev===W.Undefined&&(this._first=this._first.next,this._first.prev=W.Undefined);this._size-=1}*[Symbol.iterator](){let e=this._first;for(;e!==W.Undefined;)yield e.element,e=e.next}},Oo=globalThis.performance&&typeof globalThis.performance.now=="function",Io=class cn{static create(t){return new cn(t)}constructor(t){this._now=Oo&&t===!1?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return this._stopTime!==-1?this._stopTime-this._startTime:this._now()-this._startTime}},No=!1,js=!1,Fo=!1,di;(e=>{e.None=()=>Se.None;function t(m){if(Fo){let{onDidAddListener:g}=m,C=ji.create(),S=0;m.onDidAddListener=()=>{++S===2&&(console.warn("snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here"),C.print()),g?.()}}}function i(m,g){return u(m,()=>{},0,void 0,!0,void 0,g)}e.defer=i;function s(m){return(g,C=null,S)=>{let k=!1,v;return v=m(M=>{if(!k)return v?v.dispose():k=!0,g.call(C,M)},null,S),k&&v.dispose(),v}}e.once=s;function r(m,g,C){return d((S,k=null,v)=>m(M=>S.call(k,g(M)),null,v),C)}e.map=r;function n(m,g,C){return d((S,k=null,v)=>m(M=>{g(M),S.call(k,M)},null,v),C)}e.forEach=n;function o(m,g,C){return d((S,k=null,v)=>m(M=>g(M)&&S.call(k,M),null,v),C)}e.filter=o;function h(m){return m}e.signal=h;function l(...m){return(g,C=null,S)=>{let k=Ao(...m.map(v=>v(M=>g.call(C,M))));return c(k,S)}}e.any=l;function a(m,g,C,S){let k=C;return r(m,v=>(k=g(k,v),k),S)}e.reduce=a;function d(m,g){let C,S={onWillAddFirstListener(){C=m(k.fire,k)},onDidRemoveLastListener(){C?.dispose()}};g||t(S);let k=new se(S);return g?.add(k),k.event}function c(m,g){return g instanceof Array?g.push(m):g&&g.add(m),m}function u(m,g,C=100,S=!1,k=!1,v,M){let H,ne,Ee,Pe=0,J,We={leakWarningThreshold:v,onWillAddFirstListener(){H=m(Ge=>{Pe++,ne=g(ne,Ge),S&&!Ee&&(le.fire(ne),ne=void 0),J=()=>{let ut=ne;ne=void 0,Ee=void 0,(!S||Pe>1)&&le.fire(ut),Pe=0},typeof C=="number"?(clearTimeout(Ee),Ee=setTimeout(J,C)):Ee===void 0&&(Ee=0,queueMicrotask(J))})},onWillRemoveListener(){k&&Pe>0&&J?.()},onDidRemoveLastListener(){J=void 0,H.dispose()}};M||t(We);let le=new se(We);return M?.add(le),le.event}e.debounce=u;function f(m,g=0,C){return e.debounce(m,(S,k)=>S?(S.push(k),S):[k],g,void 0,!0,void 0,C)}e.accumulate=f;function _(m,g=(S,k)=>S===k,C){let S=!0,k;return o(m,v=>{let M=S||!g(v,k);return S=!1,k=v,M},C)}e.latch=_;function p(m,g,C){return[e.filter(m,g,C),e.filter(m,S=>!g(S),C)]}e.split=p;function y(m,g=!1,C=[],S){let k=C.slice(),v=m(ne=>{k?k.push(ne):H.fire(ne)});S&&S.add(v);let M=()=>{k?.forEach(ne=>H.fire(ne)),k=null},H=new se({onWillAddFirstListener(){v||(v=m(ne=>H.fire(ne)),S&&S.add(v))},onDidAddFirstListener(){k&&(g?setTimeout(M):M())},onDidRemoveLastListener(){v&&v.dispose(),v=null}});return S&&S.add(H),H.event}e.buffer=y;function E(m,g){return(C,S,k)=>{let v=g(new R);return m(function(M){let H=v.evaluate(M);H!==A&&C.call(S,H)},void 0,k)}}e.chain=E;let A=Symbol("HaltChainable");class R{constructor(){this.steps=[]}map(g){return this.steps.push(g),this}forEach(g){return this.steps.push(C=>(g(C),C)),this}filter(g){return this.steps.push(C=>g(C)?C:A),this}reduce(g,C){let S=C;return this.steps.push(k=>(S=g(S,k),S)),this}latch(g=(C,S)=>C===S){let C=!0,S;return this.steps.push(k=>{let v=C||!g(k,S);return C=!1,S=k,v?k:A}),this}evaluate(g){for(let C of this.steps)if(g=C(g),g===A)break;return g}}function x(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.on(g,S),v=()=>m.removeListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromNodeEventEmitter=x;function L(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.addEventListener(g,S),v=()=>m.removeEventListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromDOMEventEmitter=L;function P(m){return new Promise(g=>s(m)(g))}e.toPromise=P;function re(m){let g=new se;return m.then(C=>{g.fire(C)},()=>{g.fire(void 0)}).finally(()=>{g.dispose()}),g.event}e.fromPromise=re;function ye(m,g){return m(C=>g.fire(C))}e.forward=ye;function xe(m,g,C){return g(C),m(S=>g(S))}e.runAndSubscribe=xe;class Nt{constructor(g,C){this._observable=g,this._counter=0,this._hasChanged=!1;let S={onWillAddFirstListener:()=>{g.addObserver(this)},onDidRemoveLastListener:()=>{g.removeObserver(this)}};C||t(S),this.emitter=new se(S),C&&C.add(this.emitter)}beginUpdate(g){this._counter++}handlePossibleChange(g){}handleChange(g,C){this._hasChanged=!0}endUpdate(g){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function F(m,g){return new Nt(m,g).emitter.event}e.fromObservable=F;function pi(m){return(g,C,S)=>{let k=0,v=!1,M={beginUpdate(){k++},endUpdate(){k--,k===0&&(m.reportChanges(),v&&(v=!1,g.call(C)))},handlePossibleChange(){},handleChange(){v=!0}};m.addObserver(M),m.reportChanges();let H={dispose(){m.removeObserver(M)}};return S instanceof tt?S.add(H):Array.isArray(S)&&S.push(H),H}}e.fromObservableLight=pi})(di||(di={}));var Vi=class Yi{constructor(t){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${t}_${Yi._idPool++}`,Yi.all.add(this)}start(t){this._stopWatch=new Io,this.listenerCount=t}stop(){if(this._stopWatch){let t=this._stopWatch.elapsed();this.durations.push(t),this.elapsedOverall+=t,this.invocationCount+=1,this._stopWatch=void 0}}};Vi.all=new Set,Vi._idPool=0;var Ho=Vi,Xs=-1,dn=class un{constructor(t,i,s=(un._idPool++).toString(16).padStart(3,"0")){this._errorHandler=t,this.threshold=i,this.name=s,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(t,i){let s=this.threshold;if(s<=0||i{let n=this._stacks.get(t.value)||0;this._stacks.set(t.value,n-1)}}getMostFrequentStack(){if(!this._stacks)return;let t,i=0;for(let[s,r]of this._stacks)(!t||i{if(e instanceof $t)t(e);else for(let i=0;i{e.length!==0&&(console.warn("[LEAKING LISTENERS] GC'ed these listeners that were NOT yet disposed:"),console.warn(e.join(` -`)),e.length=0)},3e3),Ut=new FinalizationRegistry(t=>{typeof t=="string"&&e.push(t)})}var se=class{constructor(e){this._size=0,this._options=e,this._leakageMon=Ys>0||this._options?.leakWarningThreshold?new No(e?.onListenerError??Kt,this._options?.leakWarningThreshold??Ys):void 0,this._perfMon=this._options?._profName?new Io(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){if(!this._disposed){if(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners){if(Vs){let e=this._listeners;queueMicrotask(()=>{Ko(e,t=>t.stack?.print())})}this._listeners=void 0,this._size=0}this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose()}}get event(){return this._event??(this._event=(e,t,i)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let h=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(h);let l=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],a=new Ho(`${h}. HINT: Stack shows most frequent listener (${l[1]}-times)`,l[0]);return(this._options?.onListenerError||Kt)(a),Se.None}if(this._disposed)return Se.None;t&&(e=e.bind(t));let s=new $t(e),r,n;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(s.stack=Vi.create(),r=this._leakageMon.check(s.stack,this._size+1)),Vs&&(s.stack=n??Vi.create()),this._listeners?this._listeners instanceof $t?(this._deliveryQueue??(this._deliveryQueue=new $o),this._listeners=[this._listeners,s]):this._listeners.push(s):(this._options?.onWillAddFirstListener?.(this),this._listeners=s,this._options?.onDidAddFirstListener?.(this)),this._size++;let o=Ye(()=>{Ut?.unregister(o),r?.(),this._removeListener(s)});if(i instanceof et?i.add(o):Array.isArray(i)&&i.push(o),Ut){let h=new Error().stack.split(` +`))}},zo=class extends Error{constructor(e,t){super(e),this.name="ListenerLeakError",this.stack=t}},Ko=class extends Error{constructor(e,t){super(e),this.name="ListenerRefusalError",this.stack=t}},$o=0,qt=class{constructor(e){this.value=e,this.id=$o++}},Uo=2,qo=(e,t)=>{if(e instanceof qt)t(e);else for(let i=0;i{e.length!==0&&(console.warn("[LEAKING LISTENERS] GC'ed these listeners that were NOT yet disposed:"),console.warn(e.join(` +`)),e.length=0)},3e3),Vt=new FinalizationRegistry(t=>{typeof t=="string"&&e.push(t)})}var se=class{constructor(e){this._size=0,this._options=e,this._leakageMon=Xs>0||this._options?.leakWarningThreshold?new Wo(e?.onListenerError??Ut,this._options?.leakWarningThreshold??Xs):void 0,this._perfMon=this._options?._profName?new Ho(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){if(!this._disposed){if(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners){if(js){let e=this._listeners;queueMicrotask(()=>{qo(e,t=>t.stack?.print())})}this._listeners=void 0,this._size=0}this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose()}}get event(){return this._event??(this._event=(e,t,i)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let h=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(h);let l=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],a=new Ko(`${h}. HINT: Stack shows most frequent listener (${l[1]}-times)`,l[0]);return(this._options?.onListenerError||Ut)(a),Se.None}if(this._disposed)return Se.None;t&&(e=e.bind(t));let s=new qt(e),r,n;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(s.stack=ji.create(),r=this._leakageMon.check(s.stack,this._size+1)),js&&(s.stack=n??ji.create()),this._listeners?this._listeners instanceof qt?(this._deliveryQueue??(this._deliveryQueue=new Vo),this._listeners=[this._listeners,s]):this._listeners.push(s):(this._options?.onWillAddFirstListener?.(this),this._listeners=s,this._options?.onDidAddFirstListener?.(this)),this._size++;let o=je(()=>{Vt?.unregister(o),r?.(),this._removeListener(s)});if(i instanceof tt?i.add(o):Array.isArray(i)&&i.push(o),Vt){let h=new Error().stack.split(` `).slice(2,3).join(` -`).trim(),l=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(h);Ut.register(o,l?.[2]??h,o)}return o}),this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(this._size===1){this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),this._size=0;return}let t=this._listeners,i=t.indexOf(e);if(i===-1)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,t[i]=void 0;let s=this._deliveryQueue.current===this;if(this._size*zo<=t.length){let r=0;for(let n=0;n0}},$o=class{constructor(){this.i=-1,this.end=0}enqueue(e,t,i){this.i=0,this.end=i,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},Yi=class{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new se,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new se,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}getZoomLevel(t){return this.mapWindowIdToZoomLevel.get(this.getWindowId(t))??0}setZoomLevel(t,i){if(this.getZoomLevel(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToZoomLevel.set(s,t),this._onDidChangeZoomLevel.fire(s)}getZoomFactor(t){return this.mapWindowIdToZoomFactor.get(this.getWindowId(t))??1}setZoomFactor(t,i){this.mapWindowIdToZoomFactor.set(this.getWindowId(i),t)}setFullscreen(t,i){if(this.isFullscreen(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToFullScreen.set(s,t),this._onDidChangeFullscreen.fire(s)}isFullscreen(t){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(t))}getWindowId(t){return t.vscodeWindowId}};Yi.INSTANCE=new Yi;var ks=Yi;function Uo(e,t,i){typeof t=="string"&&(t=e.matchMedia(t)),t.addEventListener("change",i)}var $l=ks.INSTANCE.onDidChangeZoomLevel;function qo(e){return ks.INSTANCE.getZoomFactor(e)}var Ul=ks.INSTANCE.onDidChangeFullscreen,lt=typeof navigator=="object"?navigator.userAgent:"",ji=lt.indexOf("Firefox")>=0,qt=lt.indexOf("AppleWebKit")>=0,xs=lt.indexOf("Chrome")>=0,un=!xs&<.indexOf("Safari")>=0,ql=lt.indexOf("Electron/")>=0,Vl=lt.indexOf("Android")>=0,Vt=!1;if(typeof me.matchMedia=="function"){let e=me.matchMedia("(display-mode: standalone) or (display-mode: window-controls-overlay)"),t=me.matchMedia("(display-mode: fullscreen)");Vt=e.matches,Uo(me,e,({matches:i})=>{Vt&&t.matches||(Vt=i)})}function Vo(){return Vt}var nt="en",ti=!1,ii=!1,Et=!1,Yo=!1,_n=!1,fn=!1,jo=!1,Xo=!1,Go=!1,Jo=!1,Nt,Yt=nt,js=nt,Zo,Te,Oe=globalThis,ve;typeof Oe.vscode<"u"&&typeof Oe.vscode.process<"u"?ve=Oe.vscode.process:typeof process<"u"&&typeof process?.versions?.node=="string"&&(ve=process);var pn=typeof ve?.versions?.electron=="string",Qo=pn&&ve?.type==="renderer";if(typeof ve=="object"){ti=ve.platform==="win32",ii=ve.platform==="darwin",Et=ve.platform==="linux",Yo=Et&&!!ve.env.SNAP&&!!ve.env.SNAP_REVISION,jo=pn,Go=!!ve.env.CI||!!ve.env.BUILD_ARTIFACTSTAGINGDIRECTORY,Nt=nt,Yt=nt;let e=ve.env.VSCODE_NLS_CONFIG;if(e)try{let t=JSON.parse(e);Nt=t.userLocale,js=t.osLocale,Yt=t.resolvedLanguage||nt,Zo=t.languagePack?.translationsConfigFile}catch{}_n=!0}else typeof navigator=="object"&&!Qo?(Te=navigator.userAgent,ti=Te.indexOf("Windows")>=0,ii=Te.indexOf("Macintosh")>=0,Xo=(Te.indexOf("Macintosh")>=0||Te.indexOf("iPad")>=0||Te.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,Et=Te.indexOf("Linux")>=0,Jo=Te?.indexOf("Mobi")>=0,fn=!0,Yt=globalThis._VSCODE_NLS_LANGUAGE||nt,Nt=navigator.language.toLowerCase(),js=Nt):console.error("Unable to resolve platform.");var gi=0;ii?gi=1:ti?gi=3:Et&&(gi=2);var gn=ti,Re=ii,ea=Et,vi=_n,ta=fn&&typeof Oe.importScripts=="function",Yl=ta?Oe.origin:void 0,Me=Te,Ke=Yt,ia;(e=>{function t(){return Ke}e.value=t;function i(){return Ke.length===2?Ke==="en":Ke.length>=3?Ke[0]==="e"&&Ke[1]==="n"&&Ke[2]==="-":!1}e.isDefaultVariant=i;function s(){return Ke==="en"}e.isDefault=s})(ia||(ia={}));var sa=typeof Oe.postMessage=="function"&&!Oe.importScripts,ra=(()=>{if(sa){let e=[];Oe.addEventListener("message",i=>{if(i.data&&i.data.vscodeScheduleAsyncWork)for(let s=0,r=e.length;s{let s=++t;e.push({id:s,callback:i}),Oe.postMessage({vscodeScheduleAsyncWork:s},"*")}}return e=>setTimeout(e)})(),na=!!(Me&&Me.indexOf("Chrome")>=0),jl=!!(Me&&Me.indexOf("Firefox")>=0),Xl=!!(!na&&Me&&Me.indexOf("Safari")>=0),Gl=!!(Me&&Me.indexOf("Edg/")>=0),Jl=!!(Me&&Me.indexOf("Android")>=0),$e=typeof navigator=="object"?navigator:{},Zl={clipboard:{writeText:vi||document.queryCommandSupported&&document.queryCommandSupported("copy")||!!($e&&$e.clipboard&&$e.clipboard.writeText),readText:vi||!!($e&&$e.clipboard&&$e.clipboard.readText)},keyboard:vi||Vo()?0:$e.keyboard||un?1:2,touch:"ontouchstart"in me||$e.maxTouchPoints>0,pointerEvents:me.PointerEvent&&("ontouchstart"in me||navigator.maxTouchPoints>0)},Es=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||0}},mi=new Es,Xs=new Es,Gs=new Es,oa=new Array(230),vn;(e=>{function t(h){return mi.keyCodeToStr(h)}e.toString=t;function i(h){return mi.strToKeyCode(h)}e.fromString=i;function s(h){return Xs.keyCodeToStr(h)}e.toUserSettingsUS=s;function r(h){return Gs.keyCodeToStr(h)}e.toUserSettingsGeneral=r;function n(h){return Xs.strToKeyCode(h)||Gs.strToKeyCode(h)}e.fromUserSettings=n;function o(h){if(h>=98&&h<=113)return null;switch(h){case 16:return"Up";case 18:return"Down";case 15:return"Left";case 17:return"Right"}return mi.keyCodeToStr(h)}e.toElectronAccelerator=o})(vn||(vn={}));var aa=class mn{constructor(t,i,s,r,n){this.ctrlKey=t,this.shiftKey=i,this.altKey=s,this.metaKey=r,this.keyCode=n}equals(t){return t instanceof mn&&this.ctrlKey===t.ctrlKey&&this.shiftKey===t.shiftKey&&this.altKey===t.altKey&&this.metaKey===t.metaKey&&this.keyCode===t.keyCode}getHashCode(){let t=this.ctrlKey?"1":"0",i=this.shiftKey?"1":"0",s=this.altKey?"1":"0",r=this.metaKey?"1":"0";return`K${t}${i}${s}${r}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new ha([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}},ha=class{constructor(e){if(e.length===0)throw So("chords");this.chords=e}getHashCode(){let e="";for(let t=0,i=this.chords.length;t{function t(i){return i===e.None||i===e.Cancelled||i instanceof va?!0:!i||typeof i!="object"?!1:typeof i.isCancellationRequested=="boolean"&&typeof i.onCancellationRequested=="function"}e.isCancellationToken=t,e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:li.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:Sn})})(ga||(ga={}));var va=class{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?Sn:(this._emitter||(this._emitter=new se),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}};var Bs=class{constructor(e,t){this._isDisposed=!1,this._token=-1,typeof e=="function"&&typeof t=="number"&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new Hi("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new Hi("Calling 'setIfNotSet' on a disposed TimeoutTimer");this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},ma=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this.isDisposed)throw new Hi("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();let s=i.setInterval(()=>{e()},t);this.disposable=Ye(()=>{i.clearInterval(s),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}},Sa,Si;(function(){typeof globalThis.requestIdleCallback!="function"||typeof globalThis.cancelIdleCallback!="function"?Si=(e,t)=>{ra(()=>{if(i)return;let s=Date.now()+15;t(Object.freeze({didTimeout:!0,timeRemaining(){return Math.max(0,s-Date.now())}}))});let i=!1;return{dispose(){i||(i=!0)}}}:Si=(e,t,i)=>{let s=e.requestIdleCallback(t,typeof i=="number"?{timeout:i}:void 0),r=!1;return{dispose(){r||(r=!0,e.cancelIdleCallback(s))}}},Sa=e=>Si(globalThis,e)})();var wa;(e=>{async function t(s){let r,n=await Promise.all(s.map(o=>o.then(h=>h,h=>{r||(r=h)})));if(typeof r<"u")throw r;return n}e.settled=t;function i(s){return new Promise(async(r,n)=>{try{await s(r,n)}catch(o){n(o)}})}e.withAsyncBody=i})(wa||(wa={}));var Qs=class we{static fromArray(t){return new we(i=>{i.emitMany(t)})}static fromPromise(t){return new we(async i=>{i.emitMany(await t)})}static fromPromises(t){return new we(async i=>{await Promise.all(t.map(async s=>i.emitOne(await s)))})}static merge(t){return new we(async i=>{await Promise.all(t.map(async s=>{for await(let r of s)i.emitOne(r)}))})}constructor(t,i){this._state=0,this._results=[],this._error=null,this._onReturn=i,this._onStateChanged=new se,queueMicrotask(async()=>{let s={emitOne:r=>this.emitOne(r),emitMany:r=>this.emitMany(r),reject:r=>this.reject(r)};try{await Promise.resolve(t(s)),this.resolve()}catch(r){this.reject(r)}finally{s.emitOne=void 0,s.emitMany=void 0,s.reject=void 0}})}[Symbol.asyncIterator](){let t=0;return{next:async()=>{do{if(this._state===2)throw this._error;if(t(this._onReturn?.(),{done:!0,value:void 0})}}static map(t,i){return new we(async s=>{for await(let r of t)s.emitOne(i(r))})}map(t){return we.map(this,t)}static filter(t,i){return new we(async s=>{for await(let r of t)i(r)&&s.emitOne(r)})}filter(t){return we.filter(this,t)}static coalesce(t){return we.filter(t,i=>!!i)}coalesce(){return we.coalesce(this)}static async toPromise(t){let i=[];for await(let s of t)i.push(s);return i}toPromise(){return we.toPromise(this)}emitOne(t){this._state===0&&(this._results.push(t),this._onStateChanged.fire())}emitMany(t){this._state===0&&(this._results=this._results.concat(t),this._onStateChanged.fire())}resolve(){this._state===0&&(this._state=1,this._onStateChanged.fire())}reject(t){this._state===0&&(this._state=2,this._error=t,this._onStateChanged.fire())}};Qs.EMPTY=Qs.fromArray([]);function ba(e){return 55296<=e&&e<=56319}function er(e){return 56320<=e&&e<=57343}function ya(e,t){return(e-55296<<10)+(t-56320)+65536}function Ca(e){return Ds(e,0)}function Ds(e,t){switch(typeof e){case"object":return e===null?Ae(349,t):Array.isArray(e)?xa(e,t):Ea(e,t);case"string":return wn(e,t);case"boolean":return ka(e,t);case"number":return Ae(e,t);case"undefined":return Ae(937,t);default:return Ae(617,t)}}function Ae(e,t){return(t<<5)-t+e|0}function ka(e,t){return Ae(e?433:863,t)}function wn(e,t){t=Ae(149417,t);for(let i=0,s=e.length;iDs(s,i),t)}function Ea(e,t){return t=Ae(181387,t),Object.keys(e).sort().reduce((i,s)=>(i=wn(s,i),Ds(e[s],i)),t)}function wi(e,t,i=32){let s=i-t,r=~((1<>>s)>>>0}function tr(e,t=0,i=e.byteLength,s=0){for(let r=0;ri.toString(16).padStart(2,"0")).join(""):Ba((e>>>0).toString(16),t/4)}var Da=class bn{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(67),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(t){let i=t.length;if(i===0)return;let s=this._buff,r=this._buffLen,n=this._leftoverHighSurrogate,o,h;for(n!==0?(o=n,h=-1,n=0):(o=t.charCodeAt(0),h=0);;){let l=o;if(ba(o))if(h+1>>6,t[i++]=128|(s&63)>>>0):s<65536?(t[i++]=224|(s&61440)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0):(t[i++]=240|(s&1835008)>>>18,t[i++]=128|(s&258048)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0),i>=64&&(this._step(),i-=64,this._totalLen+=64,t[0]=t[64],t[1]=t[65],t[2]=t[66]),i}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),_t(this._h0)+_t(this._h1)+_t(this._h2)+_t(this._h3)+_t(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,tr(this._buff,this._buffLen),this._buffLen>56&&(this._step(),tr(this._buff));let t=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(t/4294967296),!1),this._buffDV.setUint32(60,t%4294967296,!1),this._step()}_step(){let t=bn._bigBlock32,i=this._buffDV;for(let c=0;c<64;c+=4)t.setUint32(c,i.getUint32(c,!1),!1);for(let c=64;c<320;c+=4)t.setUint32(c,wi(t.getUint32(c-12,!1)^t.getUint32(c-32,!1)^t.getUint32(c-56,!1)^t.getUint32(c-64,!1),1),!1);let s=this._h0,r=this._h1,n=this._h2,o=this._h3,h=this._h4,l,a,d;for(let c=0;c<80;c++)c<20?(l=r&n|~r&o,a=1518500249):c<40?(l=r^n^o,a=1859775393):c<60?(l=r&n|r&o|n&o,a=2400959708):(l=r^n^o,a=3395469782),d=wi(s,5)+l+h+a+t.getUint32(c*4,!1)&4294967295,h=o,o=n,n=wi(r,30),r=s,s=d;this._h0=this._h0+s&4294967295,this._h1=this._h1+r&4294967295,this._h2=this._h2+n&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+h&4294967295}};Da._bigBlock32=new DataView(new ArrayBuffer(320));var{registerWindow:Ql,getWindow:be,getDocument:ec,getWindows:tc,getWindowsCount:ic,getWindowId:ir,getWindowById:sc,hasWindow:rc,onDidRegisterWindow:Ra,onWillUnregisterWindow:nc,onDidUnregisterWindow:oc}=(function(){let e=new Map,t={window:me,disposables:new et};e.set(me.vscodeWindowId,t);let i=new se,s=new se,r=new se;function n(o,h){return(typeof o=="number"?e.get(o):void 0)??(h?t:void 0)}return{onDidRegisterWindow:i.event,onWillUnregisterWindow:r.event,onDidUnregisterWindow:s.event,registerWindow(o){if(e.has(o.vscodeWindowId))return Se.None;let h=new et,l={window:o,disposables:h.add(new et)};return e.set(o.vscodeWindowId,l),h.add(Ye(()=>{e.delete(o.vscodeWindowId),s.fire(o)})),h.add(B(o,Z.BEFORE_UNLOAD,()=>{r.fire(o)})),i.fire(l),h},getWindows(){return e.values()},getWindowsCount(){return e.size},getWindowId(o){return o.vscodeWindowId},hasWindow(o){return e.has(o)},getWindowById:n,getWindow(o){let h=o;if(h?.ownerDocument?.defaultView)return h.ownerDocument.defaultView.window;let l=o;return l?.view?l.view.window:me},getDocument(o){return be(o).document}}})(),Ma=class{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}};function B(e,t,i,s){return new Ma(e,t,i,s)}function La(e,t){return function(i){return t(new wt(e,i))}}function Pa(e){return function(t){return e(new Xi(t))}}var sr=function(e,t,i,s){let r=i;return t==="click"||t==="mousedown"||t==="contextmenu"?r=La(be(e),i):(t==="keydown"||t==="keypress"||t==="keyup")&&(r=Pa(i)),B(e,t,r,s)},Ta,si,Aa=class extends ma{constructor(e){super(),this.defaultTarget=e&&be(e)}cancelAndSet(e,t,i){return super.cancelAndSet(e,t,i??this.defaultTarget)}},bi=class{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){Kt(e)}}static sort(e,t){return t.priority-e.priority}};(function(){let e=new Map,t=new Map,i=new Map,s=new Map,r=n=>{i.set(n,!1);let o=e.get(n)??[];for(t.set(n,o),e.set(n,[]),s.set(n,!0);o.length>0;)o.sort(bi.sort),o.shift().execute();s.set(n,!1)};si=(n,o,h=0)=>{let l=ir(n),a=new bi(o,h),d=e.get(l);return d||(d=[],e.set(l,d)),d.push(a),i.get(l)||(i.set(l,!0),n.requestAnimationFrame(()=>r(l))),a},Ta=(n,o,h)=>{let l=ir(n);if(s.get(l)){let a=new bi(o,h),d=t.get(l);return d||(d=[],t.set(l,d)),d.push(a),a}else return si(n,o,h)}})();var rr=class jt{constructor(t,i){this.width=t,this.height=i}with(t=this.width,i=this.height){return t!==this.width||i!==this.height?new jt(t,i):this}static is(t){return typeof t=="object"&&typeof t.height=="number"&&typeof t.width=="number"}static lift(t){return t instanceof jt?t:new jt(t.width,t.height)}static equals(t,i){return t===i?!0:!t||!i?!1:t.width===i.width&&t.height===i.height}};rr.None=new rr(0,0);function Oa(e){let t=e.getBoundingClientRect(),i=be(e);return{left:t.left+i.scrollX,top:t.top+i.scrollY,width:t.width,height:t.height}}var ac=new class{constructor(){this.mutationObservers=new Map}observe(e,t,i){let s=this.mutationObservers.get(e);s||(s=new Map,this.mutationObservers.set(e,s));let r=Ca(i),n=s.get(r);if(n)n.users+=1;else{let o=new se,h=new MutationObserver(a=>o.fire(a));h.observe(e,i);let l=n={users:1,observer:h,onDidMutate:o.event};t.add(Ye(()=>{l.users-=1,l.users===0&&(o.dispose(),h.disconnect(),s?.delete(r),s?.size===0&&this.mutationObservers.delete(e))})),s.set(r,n)}return n.onDidMutate}},Z={CLICK:"click",AUXCLICK:"auxclick",DBLCLICK:"dblclick",MOUSE_UP:"mouseup",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_MOVE:"mousemove",MOUSE_OUT:"mouseout",MOUSE_ENTER:"mouseenter",MOUSE_LEAVE:"mouseleave",MOUSE_WHEEL:"wheel",POINTER_UP:"pointerup",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_LEAVE:"pointerleave",CONTEXT_MENU:"contextmenu",WHEEL:"wheel",KEY_DOWN:"keydown",KEY_PRESS:"keypress",KEY_UP:"keyup",LOAD:"load",BEFORE_UNLOAD:"beforeunload",UNLOAD:"unload",PAGE_SHOW:"pageshow",PAGE_HIDE:"pagehide",PASTE:"paste",ABORT:"abort",ERROR:"error",RESIZE:"resize",SCROLL:"scroll",FULLSCREEN_CHANGE:"fullscreenchange",WK_FULLSCREEN_CHANGE:"webkitfullscreenchange",SELECT:"select",CHANGE:"change",SUBMIT:"submit",RESET:"reset",FOCUS:"focus",FOCUS_IN:"focusin",FOCUS_OUT:"focusout",BLUR:"blur",INPUT:"input",STORAGE:"storage",DRAG_START:"dragstart",DRAG:"drag",DRAG_ENTER:"dragenter",DRAG_LEAVE:"dragleave",DRAG_OVER:"dragover",DROP:"drop",DRAG_END:"dragend",ANIMATION_START:qt?"webkitAnimationStart":"animationstart",ANIMATION_END:qt?"webkitAnimationEnd":"animationend",ANIMATION_ITERATION:qt?"webkitAnimationIteration":"animationiteration"},Ia=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function yn(e,t,i,...s){let r=Ia.exec(t);if(!r)throw new Error("Bad use of emmet");let n=r[1]||"div",o;return e!=="http://www.w3.org/1999/xhtml"?o=document.createElementNS(e,n):o=document.createElement(n),r[3]&&(o.id=r[3]),r[4]&&(o.className=r[4].replace(/\./g," ").trim()),i&&Object.entries(i).forEach(([h,l])=>{typeof l>"u"||(/^on\w+$/.test(h)?o[h]=l:h==="selected"?l&&o.setAttribute(h,"true"):o.setAttribute(h,l))}),o.append(...s),o}function Na(e,t,...i){return yn("http://www.w3.org/1999/xhtml",e,t,...i)}Na.SVG=function(e,t,...i){return yn("http://www.w3.org/2000/svg",e,t,...i)};var Fa=class{constructor(e){this.domNode=e,this._maxWidth="",this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._paddingTop="",this._paddingLeft="",this._paddingBottom="",this._paddingRight="",this._fontFamily="",this._fontWeight="",this._fontSize="",this._fontStyle="",this._fontFeatureSettings="",this._fontVariationSettings="",this._textDecoration="",this._lineHeight="",this._letterSpacing="",this._className="",this._display="",this._position="",this._visibility="",this._color="",this._backgroundColor="",this._layerHint=!1,this._contain="none",this._boxShadow=""}setMaxWidth(e){let t=de(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){let t=de(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){let t=de(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){let t=de(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){let t=de(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){let t=de(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){let t=de(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){let t=de(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){let t=de(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){let t=de(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){let t=de(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){let t=de(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){let t=de(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){let t=de(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?"translate3d(0px, 0px, 0px)":"")}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}};function de(e){return typeof e=="number"?`${e}px`:e}function Bt(e){return new Fa(e)}var Cn=class{constructor(){this._hooks=new et,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let i=this._onStopCallback;this._onStopCallback=null,e&&i&&i(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,i,s,r){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=s,this._onStopCallback=r;let n=e;try{e.setPointerCapture(t),this._hooks.add(Ye(()=>{try{e.releasePointerCapture(t)}catch{}}))}catch{n=be(e)}this._hooks.add(B(n,Z.POINTER_MOVE,o=>{if(o.buttons!==i){this.stopMonitoring(!0);return}o.preventDefault(),this._pointerMoveCallback(o)})),this._hooks.add(B(n,Z.POINTER_UP,o=>this.stopMonitoring(!0)))}};function Ha(e,t,i){let s=null,r=null;if(typeof i.value=="function"?(s="value",r=i.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof i.get=="function"&&(s="get",r=i.get),!r)throw new Error("not supported");let n=`$memoize$${t}`;i[s]=function(...o){return this.hasOwnProperty(n)||Object.defineProperty(this,n,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,o)}),this[n]}}var Ce;(e=>(e.Tap="-xterm-gesturetap",e.Change="-xterm-gesturechange",e.Start="-xterm-gesturestart",e.End="-xterm-gesturesend",e.Contextmenu="-xterm-gesturecontextmenu"))(Ce||(Ce={}));var bt=class ae extends Se{constructor(){super(),this.dispatched=!1,this.targets=new qs,this.ignoreTargets=new qs,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(li.runAndSubscribe(Ra,({window:t,disposables:i})=>{i.add(B(t.document,"touchstart",s=>this.onTouchStart(s),{passive:!1})),i.add(B(t.document,"touchend",s=>this.onTouchEnd(t,s))),i.add(B(t.document,"touchmove",s=>this.onTouchMove(s),{passive:!1}))},{window:me,disposables:this._store}))}static addTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Us(new ae));let i=ae.INSTANCE.targets.push(t);return Ye(i)}static ignoreTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Us(new ae));let i=ae.INSTANCE.ignoreTargets.push(t);return Ye(i)}static isTouchDevice(){return"ontouchstart"in me||navigator.maxTouchPoints>0}dispose(){this.handle&&(this.handle.dispose(),this.handle=null),super.dispose()}onTouchStart(t){let i=Date.now();this.handle&&(this.handle.dispose(),this.handle=null);for(let s=0,r=t.targetTouches.length;s=ae.HOLD_DELAY&&Math.abs(l.initialPageX-fe(l.rollingPageX))<30&&Math.abs(l.initialPageY-fe(l.rollingPageY))<30){let d=this.newGestureEvent(Ce.Contextmenu,l.initialTarget);d.pageX=fe(l.rollingPageX),d.pageY=fe(l.rollingPageY),this.dispatchEvent(d)}else if(r===1){let d=fe(l.rollingPageX),c=fe(l.rollingPageY),u=fe(l.rollingTimestamps)-l.rollingTimestamps[0],f=d-l.rollingPageX[0],_=c-l.rollingPageY[0],p=[...this.targets].filter(y=>l.initialTarget instanceof Node&&y.contains(l.initialTarget));this.inertia(t,p,s,Math.abs(f)/u,f>0?1:-1,d,Math.abs(_)/u,_>0?1:-1,c)}this.dispatchEvent(this.newGestureEvent(Ce.End,l.initialTarget)),delete this.activeTouches[h.identifier]}this.dispatched&&(i.preventDefault(),i.stopPropagation(),this.dispatched=!1)}newGestureEvent(t,i){let s=document.createEvent("CustomEvent");return s.initEvent(t,!1,!0),s.initialTarget=i,s.tapCount=0,s}dispatchEvent(t){if(t.type===Ce.Tap){let i=new Date().getTime(),s=0;i-this._lastSetTapCountTime>ae.CLEAR_TAP_COUNT_TIME?s=1:s=2,this._lastSetTapCountTime=i,t.tapCount=s}else(t.type===Ce.Change||t.type===Ce.Contextmenu)&&(this._lastSetTapCountTime=0);if(t.initialTarget instanceof Node){for(let s of this.ignoreTargets)if(s.contains(t.initialTarget))return;let i=[];for(let s of this.targets)if(s.contains(t.initialTarget)){let r=0,n=t.initialTarget;for(;n&&n!==s;)r++,n=n.parentElement;i.push([r,s])}i.sort((s,r)=>s[0]-r[0]);for(let[s,r]of i)r.dispatchEvent(t),this.dispatched=!0}}inertia(t,i,s,r,n,o,h,l,a){this.handle=si(t,()=>{let d=Date.now(),c=d-s,u=0,f=0,_=!0;r+=ae.SCROLL_FRICTION*c,h+=ae.SCROLL_FRICTION*c,r>0&&(_=!1,u=n*r*c),h>0&&(_=!1,f=l*h*c);let p=this.newGestureEvent(Ce.Change);p.translationX=u,p.translationY=f,i.forEach(y=>y.dispatchEvent(p)),_||this.inertia(t,i,d,r,n,o+u,h,l,a+f)})}onTouchMove(t){let i=Date.now();for(let s=0,r=t.changedTouches.length;s3&&(o.rollingPageX.shift(),o.rollingPageY.shift(),o.rollingTimestamps.shift()),o.rollingPageX.push(n.pageX),o.rollingPageY.push(n.pageY),o.rollingTimestamps.push(i)}this.dispatched&&(t.preventDefault(),t.stopPropagation(),this.dispatched=!1)}};bt.SCROLL_FRICTION=-.005,bt.HOLD_DELAY=700,bt.CLEAR_TAP_COUNT_TIME=400,K([Ha],bt,"isTouchDevice",1);var kn=bt,Rs=class extends Se{onclick(e,t){this._register(B(e,Z.CLICK,i=>t(new wt(be(e),i))))}onmousedown(e,t){this._register(B(e,Z.MOUSE_DOWN,i=>t(new wt(be(e),i))))}onmouseover(e,t){this._register(B(e,Z.MOUSE_OVER,i=>t(new wt(be(e),i))))}onmouseleave(e,t){this._register(B(e,Z.MOUSE_LEAVE,i=>t(new wt(be(e),i))))}onkeydown(e,t){this._register(B(e,Z.KEY_DOWN,i=>t(new Xi(i))))}onkeyup(e,t){this._register(B(e,Z.KEY_UP,i=>t(new Xi(i))))}oninput(e,t){this._register(B(e,Z.INPUT,t))}onblur(e,t){this._register(B(e,Z.BLUR,t))}onfocus(e,t){this._register(B(e,Z.FOCUS,t))}onchange(e,t){this._register(B(e,Z.CHANGE,t))}ignoreGesture(e){return kn.ignoreTarget(e)}},nr=11,Wa=class extends Rs{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=e.bgWidth+"px",this.bgDomNode.style.height=e.bgHeight+"px",typeof e.top<"u"&&(this.bgDomNode.style.top="0px"),typeof e.left<"u"&&(this.bgDomNode.style.left="0px"),typeof e.bottom<"u"&&(this.bgDomNode.style.bottom="0px"),typeof e.right<"u"&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=e.className,this.domNode.style.position="absolute",this.domNode.style.width=nr+"px",this.domNode.style.height=nr+"px",typeof e.top<"u"&&(this.domNode.style.top=e.top+"px"),typeof e.left<"u"&&(this.domNode.style.left=e.left+"px"),typeof e.bottom<"u"&&(this.domNode.style.bottom=e.bottom+"px"),typeof e.right<"u"&&(this.domNode.style.right=e.right+"px"),this._pointerMoveMonitor=this._register(new Cn),this._register(sr(this.bgDomNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._register(sr(this.domNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._pointerdownRepeatTimer=this._register(new Aa),this._pointerdownScheduleRepeatTimer=this._register(new Bs)}_arrowPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,be(e))};this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(t,200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,i=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault()}},za=class Gi{constructor(t,i,s,r,n,o,h){this._forceIntegerValues=t,this._scrollStateBrand=void 0,this._forceIntegerValues&&(i=i|0,s=s|0,r=r|0,n=n|0,o=o|0,h=h|0),this.rawScrollLeft=r,this.rawScrollTop=h,i<0&&(i=0),r+i>s&&(r=s-i),r<0&&(r=0),n<0&&(n=0),h+n>o&&(h=o-n),h<0&&(h=0),this.width=i,this.scrollWidth=s,this.scrollLeft=r,this.height=n,this.scrollHeight=o,this.scrollTop=h}equals(t){return this.rawScrollLeft===t.rawScrollLeft&&this.rawScrollTop===t.rawScrollTop&&this.width===t.width&&this.scrollWidth===t.scrollWidth&&this.scrollLeft===t.scrollLeft&&this.height===t.height&&this.scrollHeight===t.scrollHeight&&this.scrollTop===t.scrollTop}withScrollDimensions(t,i){return new Gi(this._forceIntegerValues,typeof t.width<"u"?t.width:this.width,typeof t.scrollWidth<"u"?t.scrollWidth:this.scrollWidth,i?this.rawScrollLeft:this.scrollLeft,typeof t.height<"u"?t.height:this.height,typeof t.scrollHeight<"u"?t.scrollHeight:this.scrollHeight,i?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new Gi(this._forceIntegerValues,this.width,this.scrollWidth,typeof t.scrollLeft<"u"?t.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof t.scrollTop<"u"?t.scrollTop:this.rawScrollTop)}createScrollEvent(t,i){let s=this.width!==t.width,r=this.scrollWidth!==t.scrollWidth,n=this.scrollLeft!==t.scrollLeft,o=this.height!==t.height,h=this.scrollHeight!==t.scrollHeight,l=this.scrollTop!==t.scrollTop;return{inSmoothScrolling:i,oldWidth:t.width,oldScrollWidth:t.scrollWidth,oldScrollLeft:t.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:t.height,oldScrollHeight:t.scrollHeight,oldScrollTop:t.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:s,scrollWidthChanged:r,scrollLeftChanged:n,heightChanged:o,scrollHeightChanged:h,scrollTopChanged:l}}},Ka=class extends Se{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new za(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let i=this._state.withScrollDimensions(e,t);this._setState(i,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>"u"?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>"u"?this._smoothScrolling.to.scrollTop:e.scrollTop};let i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;t?s=new ar(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):s=this._smoothScrolling.combine(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{let i=this._state.withScrollPosition(e);this._smoothScrolling=ar.start(this._state,i,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),!!this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}},or=class{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}};function yi(e,t){let i=t-e;return function(s){return e+i*qa(s)}}function $a(e,t,i){return function(s){return s2.5*s){let r,n;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" fade":"")))}},Ya=140,xn=class extends Rs{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new Va(e.visibility,"visible scrollbar "+e.extraScrollbarClassName,"invisible scrollbar "+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new Cn),this._shouldRender=!0,this.domNode=Bt(document.createElement("div")),this.domNode.setAttribute("role","presentation"),this.domNode.setAttribute("aria-hidden","true"),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition("absolute"),this._register(B(this.domNode.domNode,Z.POINTER_DOWN,t=>this._domNodePointerDown(t)))}_createArrow(e){let t=this._register(new Wa(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,i,s){this.slider=Bt(document.createElement("div")),this.slider.setClassName("slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),typeof i=="number"&&this.slider.setWidth(i),typeof s=="number"&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(B(this.slider.domNode,Z.POINTER_DOWN,r=>{r.button===0&&(r.preventDefault(),this._sliderPointerDown(r))})),this.onclick(this.slider.domNode,r=>{r.leftButton&&r.stopPropagation()})}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){let t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?e.button===0&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,i;if(e.target===this.domNode.domNode&&typeof e.offsetX=="number"&&typeof e.offsetY=="number")t=e.offsetX,i=e.offsetY;else{let r=Oa(this.domNode.domNode);t=e.pageX-r.left,i=e.pageY-r.top}let s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,r=>{let n=this._sliderOrthogonalPointerPosition(r),o=Math.abs(n-i);if(gn&&o>Ya){this._setDesiredScrollPositionNow(s.getScrollPosition());return}let h=this._sliderPointerPosition(r)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(h))},()=>{this.slider.toggleClassName("active",!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(e){let t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}},En=class Zi{constructor(t,i,s,r,n,o){this._scrollbarSize=Math.round(i),this._oppositeScrollbarSize=Math.round(s),this._arrowSize=Math.round(t),this._visibleSize=r,this._scrollSize=n,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new Zi(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(t){let i=Math.round(t);return this._visibleSize!==i?(this._visibleSize=i,this._refreshComputedValues(),!0):!1}setScrollSize(t){let i=Math.round(t);return this._scrollSize!==i?(this._scrollSize=i,this._refreshComputedValues(),!0):!1}setScrollPosition(t){let i=Math.round(t);return this._scrollPosition!==i?(this._scrollPosition=i,this._refreshComputedValues(),!0):!1}setScrollbarSize(t){this._scrollbarSize=Math.round(t)}setOppositeScrollbarSize(t){this._oppositeScrollbarSize=Math.round(t)}static _computeValues(t,i,s,r,n){let o=Math.max(0,s-t),h=Math.max(0,o-2*i),l=r>0&&r>s;if(!l)return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(h),computedSliderRatio:0,computedSliderPosition:0};let a=Math.round(Math.max(20,Math.floor(s*h/r))),d=(h-a)/(r-s),c=n*d;return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(a),computedSliderRatio:d,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){let t=Zi._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=t.computedAvailableSize,this._computedIsNeeded=t.computedIsNeeded,this._computedSliderSize=t.computedSliderSize,this._computedSliderRatio=t.computedSliderRatio,this._computedSliderPosition=t.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize-this._computedSliderSize/2;return Math.round(i/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize,s=this._scrollPosition;return i0&&Math.abs(t.deltaY)>0)return 1;let s=.5;if((!this._isAlmostInt(t.deltaX)||!this._isAlmostInt(t.deltaY))&&(s+=.25),i){let r=Math.abs(t.deltaX),n=Math.abs(t.deltaY),o=Math.abs(i.deltaX),h=Math.abs(i.deltaY),l=Math.max(Math.min(r,o),1),a=Math.max(Math.min(n,h),1),d=Math.max(r,o),c=Math.max(n,h);d%l===0&&c%a===0&&(s-=.5)}return Math.min(Math.max(s,0),1)}_isAlmostInt(t){return Math.abs(Math.round(t)-t)<.01}};Qi.INSTANCE=new Qi;var Za=Qi,Qa=class extends Rs{constructor(e,t,i){super(),this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new se),this.onWillScroll=this._onWillScroll.event,this._options=th(t),this._scrollable=i,this._register(this._scrollable.onScroll(r=>{this._onWillScroll.fire(r),this._onDidScroll(r),this._onScroll.fire(r)}));let s={onMouseWheel:r=>this._onMouseWheel(r),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new Xa(this._scrollable,this._options,s)),this._horizontalScrollbar=this._register(new ja(this._scrollable,this._options,s)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=Bt(document.createElement("div")),this._leftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=Bt(document.createElement("div")),this._topShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=Bt(document.createElement("div")),this._topLeftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,r=>this._onMouseOver(r)),this.onmouseleave(this._listenOnDomNode,r=>this._onMouseLeave(r)),this._hideTimeout=this._register(new Bs),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=ei(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,Re&&(this._options.className+=" mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){typeof e.handleMouseWheel<"u"&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<"u"&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<"u"&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<"u"&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<"u"&&(this._options.horizontal=e.horizontal),typeof e.vertical<"u"&&(this._options.vertical=e.vertical),typeof e.horizontalScrollbarSize<"u"&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<"u"&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<"u"&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new Zs(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=ei(this._mouseWheelToDispose),e)){let t=i=>{this._onMouseWheel(new Zs(i))};this._mouseWheelToDispose.push(B(this._listenOnDomNode,Z.MOUSE_WHEEL,t,{passive:!1}))}}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=Za.INSTANCE;lr&&t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let r=e.deltaY*this._options.mouseWheelScrollSensitivity,n=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&n+r===0?n=r=0:Math.abs(r)>=Math.abs(n)?n=0:r=0),this._options.flipAxes&&([r,n]=[n,r]);let o=!Re&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||o)&&!n&&(n=r,r=0),e.browserEvent&&e.browserEvent.altKey&&(n=n*this._options.fastScrollSensitivity,r=r*this._options.fastScrollSensitivity);let h=this._scrollable.getFutureScrollPosition(),l={};if(r){let a=hr*r,d=h.scrollTop-(a<0?Math.floor(a):Math.ceil(a));this._verticalScrollbar.writeScrollPosition(l,d)}if(n){let a=hr*n,d=h.scrollLeft-(a<0?Math.floor(a):Math.ceil(a));this._horizontalScrollbar.writeScrollPosition(l,d)}l=this._scrollable.validateScrollPosition(l),(h.scrollLeft!==l.scrollLeft||h.scrollTop!==l.scrollTop)&&(lr&&this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(l):this._scrollable.setScrollPositionNow(l),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" left":"",r=t?" top":"",n=i||t?" top-left-corner":"";this._leftShadowDomNode.setClassName(`shadow${s}`),this._topShadowDomNode.setClassName(`shadow${r}`),this._topLeftShadowDomNode.setClassName(`shadow${n}${r}${s}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),Ga)}},eh=class extends Qa{constructor(e,t,i){super(e,t,i)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function th(e){let t={lazyRender:typeof e.lazyRender<"u"?e.lazyRender:!1,className:typeof e.className<"u"?e.className:"",useShadows:typeof e.useShadows<"u"?e.useShadows:!0,handleMouseWheel:typeof e.handleMouseWheel<"u"?e.handleMouseWheel:!0,flipAxes:typeof e.flipAxes<"u"?e.flipAxes:!1,consumeMouseWheelIfScrollbarIsNeeded:typeof e.consumeMouseWheelIfScrollbarIsNeeded<"u"?e.consumeMouseWheelIfScrollbarIsNeeded:!1,alwaysConsumeMouseWheel:typeof e.alwaysConsumeMouseWheel<"u"?e.alwaysConsumeMouseWheel:!1,scrollYToX:typeof e.scrollYToX<"u"?e.scrollYToX:!1,mouseWheelScrollSensitivity:typeof e.mouseWheelScrollSensitivity<"u"?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof e.fastScrollSensitivity<"u"?e.fastScrollSensitivity:5,scrollPredominantAxis:typeof e.scrollPredominantAxis<"u"?e.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof e.mouseWheelSmoothScroll<"u"?e.mouseWheelSmoothScroll:!0,arrowSize:typeof e.arrowSize<"u"?e.arrowSize:11,listenOnDomNode:typeof e.listenOnDomNode<"u"?e.listenOnDomNode:null,horizontal:typeof e.horizontal<"u"?e.horizontal:1,horizontalScrollbarSize:typeof e.horizontalScrollbarSize<"u"?e.horizontalScrollbarSize:10,horizontalSliderSize:typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:0,horizontalHasArrows:typeof e.horizontalHasArrows<"u"?e.horizontalHasArrows:!1,vertical:typeof e.vertical<"u"?e.vertical:1,verticalScrollbarSize:typeof e.verticalScrollbarSize<"u"?e.verticalScrollbarSize:10,verticalHasArrows:typeof e.verticalHasArrows<"u"?e.verticalHasArrows:!1,verticalSliderSize:typeof e.verticalSliderSize<"u"?e.verticalSliderSize:0,scrollByPage:typeof e.scrollByPage<"u"?e.scrollByPage:!1};return t.horizontalSliderSize=typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof e.verticalSliderSize<"u"?e.verticalSliderSize:t.verticalScrollbarSize,Re&&(t.className+=" mac"),t}var b=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event?this._event:(this._event=(e,t,i)=>{if(this._disposed)return q(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let r=q(()=>{let n=this._listeners.indexOf(s);n!==-1&&this._listeners.splice(n,1)});return i&&(Array.isArray(i)?i.push(r):i.add(r)),r},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:i}=this._listeners[0];t.call(i,e);return}default:{let t=this._listeners.slice();for(let{fn:i,thisArgs:s}of t)i.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},_e;(e=>{function t(n,o){return n(h=>o.fire(h))}e.forward=t;function i(n,o){return(h,l,a)=>n(d=>h.call(l,o(d)),void 0,a)}e.map=i;function s(...n){return(o,h,l)=>{let a=new bs;for(let d of n)a.add(d(c=>o.call(h,c)));return l&&(Array.isArray(l)?l.push(a):l.add(a)),a}}e.any=s;function r(n,o){return o(void 0),n(h=>o(h))}e.runAndSubscribe=r})(_e||(_e={}));var es=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._bufferService=i,this._optionsService=o,this._renderService=h,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;let l=this._register(new Ka({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:a=>si(s.window,a)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{l.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new eh(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},l)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","overviewRuler"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(r.onProtocolChange(a=>{this._scrollableElement.updateOptions({handleMouseWheel:!(a&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{e.style.backgroundColor=n.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=n.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(q(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register(q(()=>this._styleElement.remove())),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .scrollbar > .slider {",` background: ${n.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider:hover {",` background: ${n.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider.active {",` background: ${n.colors.scrollbarSliderActiveBackground.css};`,"}"].join(` -`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._scrollableElement.onScroll(a=>this._handleScroll(a))),this._register(kn.addTarget(t)),this._register(B(t,Ce.Change,a=>this._handleGestureChange(a)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){!this._renderService||this._isSyncing||(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;i!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}_handleGestureChange(e){e.preventDefault(),e.stopPropagation();let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e.translationY})}};es=K([w(2,ce),w(3,Ie),w(4,Xr),w(5,ht),w(6,he),w(7,Ne)],es);var ts=class extends I{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(n=>this._removeDecoration(n))),this._register(q(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,i.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;let i=e.options.x??0;(e.options.anchor||"left")==="right"?t.style.right=i?`${i*this._renderService.dimensions.css.cell.width}px`:"":t.style.left=i?`${i*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};ts=K([w(1,ce),w(2,Ie),w(3,Tt),w(4,Ne)],ts);var ih=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(let t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position)){this._addLineToZone(t,e.marker.line);return}}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}},Be={full:0,left:0,center:0,right:0},Ue={full:0,left:0,center:0,right:0},ft={full:0,left:0,center:0,right:0},ri=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=o,this._coreBrowserService=h,this._colorZoneStore=new ih,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(q(()=>this._canvas?.remove()));let l=this._canvas.getContext("2d");if(l)this._ctx=l;else throw new Error("Ctx cannot be null");this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onRender(()=>{(!this._containerHeight||this._containerHeight!==this._screenElement.clientHeight)&&(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("overviewRuler",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._queueRefresh(!0)}get _width(){return this._optionsService.options.overviewRuler?.width||0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);Ue.full=this._canvas.width,Ue.left=e,Ue.center=t,Ue.right=e,this._refreshDrawHeightConstants(),ft.full=1,ft.left=1,ft.center=1+Ue.left,ft.right=1+Ue.left+Ue.center}_refreshDrawHeightConstants(){Be.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);Be.left=t,Be.center=t,Be.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let t of this._decorationService.decorations)this._colorZoneStore.addDecoration(t);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!=="full"&&this._renderColorZone(t);for(let t of e)t.position==="full"&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(ft[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-Be[e.position||"full"]/2),Ue[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+Be[e.position||"full"]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};ri=K([w(2,ce),w(3,Tt),w(4,Ne),w(5,he),w(6,ht),w(7,Ie)],ri);var is=class{constructor(e,t,i,s,r,n){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=n,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(e.keyCode===20||e.keyCode===229||e.keyCode===16||e.keyCode===17||e.keyCode===18)return!1;this._finalizeComposition(!1)}return e.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){let t={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let i;t.start+=this._dataAlreadySent.length,this._isComposing?i=this._textarea.value.substring(t.start,this._compositionPosition.start):i=this._textarea.value.substring(t.start),i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;let t=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(t,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;let e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){let t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};is=K([w(2,ce),w(3,he),w(4,je),w(5,Ne)],is);var ee=0,te=0,ie=0,$=0,cr={css:"#00000000",rgba:0},V;(e=>{function t(r,n,o,h){return h!==void 0?`#${Ge(r)}${Ge(n)}${Ge(o)}${Ge(h)}`:`#${Ge(r)}${Ge(n)}${Ge(o)}`}e.toCss=t;function i(r,n,o,h=255){return(r<<24|n<<16|o<<8|h)>>>0}e.toRgba=i;function s(r,n,o,h){return{css:e.toCss(r,n,o,h),rgba:e.toRgba(r,n,o,h)}}e.toColor=s})(V||(V={}));var N;(e=>{function t(l,a){if($=(a.rgba&255)/255,$===1)return{css:a.css,rgba:a.rgba};let d=a.rgba>>24&255,c=a.rgba>>16&255,u=a.rgba>>8&255,f=l.rgba>>24&255,_=l.rgba>>16&255,p=l.rgba>>8&255;ee=f+Math.round((d-f)*$),te=_+Math.round((c-_)*$),ie=p+Math.round((u-p)*$);let y=V.toCss(ee,te,ie),E=V.toRgba(ee,te,ie);return{css:y,rgba:E}}e.blend=t;function i(l){return(l.rgba&255)===255}e.isOpaque=i;function s(l,a,d){let c=Xt.ensureContrastRatio(l.rgba,a.rgba,d);if(c)return V.toColor(c>>24&255,c>>16&255,c>>8&255)}e.ensureContrastRatio=s;function r(l){let a=(l.rgba|255)>>>0;return[ee,te,ie]=Xt.toChannels(a),{css:V.toCss(ee,te,ie),rgba:a}}e.opaque=r;function n(l,a){return $=Math.round(a*255),[ee,te,ie]=Xt.toChannels(l.rgba),{css:V.toCss(ee,te,ie,$),rgba:V.toRgba(ee,te,ie,$)}}e.opacity=n;function o(l,a){return $=l.rgba&255,n(l,$*a/255)}e.multiplyOpacity=o;function h(l){return[l.rgba>>24&255,l.rgba>>16&255,l.rgba>>8&255]}e.toColorRGB=h})(N||(N={}));var z;(e=>{let t,i;try{let r=document.createElement("canvas");r.width=1,r.height=1;let n=r.getContext("2d",{willReadFrequently:!0});n&&(t=n,t.globalCompositeOperation="copy",i=t.createLinearGradient(0,0,1,1))}catch{}function s(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),V.toColor(ee,te,ie);case 5:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),$=parseInt(r.slice(4,5).repeat(2),16),V.toColor(ee,te,ie,$);case 7:return{css:r,rgba:(parseInt(r.slice(1),16)<<8|255)>>>0};case 9:return{css:r,rgba:parseInt(r.slice(1),16)>>>0}}let n=r.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(n)return ee=parseInt(n[1]),te=parseInt(n[2]),ie=parseInt(n[3]),$=Math.round((n[5]===void 0?1:parseFloat(n[5]))*255),V.toColor(ee,te,ie,$);if(r==="transparent")return{css:"transparent",rgba:0};if(!t||!i)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=i,t.fillStyle=r,typeof t.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[ee,te,ie,$]=t.getImageData(0,0,1,1).data,$!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:V.toRgba(ee,te,ie,$),css:r}}e.toColor=s})(z||(z={}));var oe;(e=>{function t(s){return i(s>>16&255,s>>8&255,s&255)}e.relativeLuminance=t;function i(s,r,n){let o=s/255,h=r/255,l=n/255,a=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),d=h<=.03928?h/12.92:Math.pow((h+.055)/1.055,2.4),c=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4);return a*.2126+d*.7152+c*.0722}e.relativeLuminance2=i})(oe||(oe={}));var Xt;(e=>{function t(o,h){if($=(h&255)/255,$===1)return h;let l=h>>24&255,a=h>>16&255,d=h>>8&255,c=o>>24&255,u=o>>16&255,f=o>>8&255;return ee=c+Math.round((l-c)*$),te=u+Math.round((a-u)*$),ie=f+Math.round((d-f)*$),V.toRgba(ee,te,ie)}e.blend=t;function i(o,h,l){let a=oe.relativeLuminance(o>>8),d=oe.relativeLuminance(h>>8);if(Pe(a,d)>8));if(_>8));return _>y?f:p}return f}let c=r(o,h,l),u=Pe(a,oe.relativeLuminance(c>>8));if(u>8));return u>_?c:f}return c}}e.ensureContrastRatio=i;function s(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Pe(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p0||f>0||_>0);)u-=Math.max(0,Math.ceil(u*.1)),f-=Math.max(0,Math.ceil(f*.1)),_-=Math.max(0,Math.ceil(_*.1)),p=Pe(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));return(u<<24|f<<16|_<<8|255)>>>0}e.reduceLuminance=s;function r(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Pe(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p>>0}e.increaseLuminance=r;function n(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}e.toChannels=n})(Xt||(Xt={}));function Ge(e){let t=e.toString(16);return t.length<2?"0"+t:t}function Pe(e,t){return e1){let d=this._getJoinedRanges(s,o,n,t,r);for(let c=0;c1){let a=this._getJoinedRanges(s,o,n,t,r);for(let d=0;d=Ot,k=m,v=this._workCell;if(u.length>0&&m===u[0][0]&&S){let T=u.shift(),fi=this._isCellInSelection(T[0],t);for(A=T[0]+1;A=T[1]),S?(C=!0,v=new sh(this._workCell,e.translateToString(!0,T[0],T[1]),T[1]-T[0]),k=T[1]-1,g=v.getWidth()):Ot=T[1]}let M=this._isCellInSelection(m,t),H=i&&m===n,ne=_i&&m>=a&&m<=d,Ee=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{Ee=!0});let Le=v.getChars()||" ";if(Le===" "&&(v.isUnderline()||v.isOverline())&&(Le="\xA0"),xe=g*h-l.get(Le,v.isBold(),v.isItalic()),!p)p=this._document.createElement("span");else if(y&&(M&&ye||!M&&!ye&&v.bg===R)&&(M&&ye&&f.selectionForeground||v.fg===x)&&v.extended.ext===L&&ne===P&&xe===re&&!H&&!C&&!Ee&&S){v.isInvisible()?E+=" ":E+=Le,y++;continue}else y&&(p.textContent=E),p=this._document.createElement("span"),y=0,E="";if(R=v.bg,x=v.fg,L=v.extended.ext,P=ne,re=xe,ye=M,C&&n>=m&&n<=k&&(n=m),!this._coreService.isCursorHidden&&H&&this._coreService.isCursorInitialized){if(F.push("xterm-cursor"),this._coreBrowserService.isFocused)o&&F.push("xterm-cursor-blink"),F.push(s==="bar"?"xterm-cursor-bar":s==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":F.push("xterm-cursor-outline");break;case"block":F.push("xterm-cursor-block");break;case"bar":F.push("xterm-cursor-bar");break;case"underline":F.push("xterm-cursor-underline");break;default:break}}if(v.isBold()&&F.push("xterm-bold"),v.isItalic()&&F.push("xterm-italic"),v.isDim()&&F.push("xterm-dim"),v.isInvisible()?E=" ":E=v.getChars()||" ",v.isUnderline()&&(F.push(`xterm-underline-${v.extended.underlineStyle}`),E===" "&&(E="\xA0"),!v.isUnderlineColorDefault()))if(v.isUnderlineColorRGB())p.style.textDecorationColor=`rgb(${Pt.toColorRGB(v.getUnderlineColor()).join(",")})`;else{let T=v.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&v.isBold()&&T<8&&(T+=8),p.style.textDecorationColor=f.ansi[T].css}v.isOverline()&&(F.push("xterm-overline"),E===" "&&(E="\xA0")),v.isStrikethrough()&&F.push("xterm-strikethrough"),ne&&(p.style.textDecoration="underline");let J=v.getFgColor(),He=v.getFgColorMode(),le=v.getBgColor(),Xe=v.getBgColorMode(),dt=!!v.isInverse();if(dt){let T=J;J=le,le=T;let fi=He;He=Xe,Xe=fi}let We,It,ut=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{T.options.layer!=="top"&&ut||(T.backgroundColorRGB&&(Xe=50331648,le=T.backgroundColorRGB.rgba>>8&16777215,We=T.backgroundColorRGB),T.foregroundColorRGB&&(He=50331648,J=T.foregroundColorRGB.rgba>>8&16777215,It=T.foregroundColorRGB),ut=T.options.layer==="top")}),!ut&&M&&(We=this._coreBrowserService.isFocused?f.selectionBackgroundOpaque:f.selectionInactiveBackgroundOpaque,le=We.rgba>>8&16777215,Xe=50331648,ut=!0,f.selectionForeground&&(He=50331648,J=f.selectionForeground.rgba>>8&16777215,It=f.selectionForeground)),ut&&F.push("xterm-decoration-top");let ze;switch(Xe){case 16777216:case 33554432:ze=f.ansi[le],F.push(`xterm-bg-${le}`);break;case 50331648:ze=V.toColor(le>>16,le>>8&255,le&255),this._addStyle(p,`background-color:#${(le>>>0).toString(16).padStart(6,"0")}`);break;default:dt?(ze=f.foreground,F.push("xterm-bg-257")):ze=f.background}switch(We||v.isDim()&&(We=N.multiplyOpacity(ze,.5)),He){case 16777216:case 33554432:v.isBold()&&J<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(J+=8),this._applyMinimumContrast(p,ze,f.ansi[J],v,We,void 0)||F.push(`xterm-fg-${J}`);break;case 50331648:let T=V.toColor(J>>16&255,J>>8&255,J&255);this._applyMinimumContrast(p,ze,T,v,We,It)||this._addStyle(p,`color:#${J.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(p,ze,f.foreground,v,We,It)||dt&&F.push("xterm-fg-257")}F.length&&(p.className=F.join(" "),F.length=0),!H&&!C&&!Ee&&S?y++:p.textContent=E,xe!==this.defaultSpacing&&(p.style.letterSpacing=`${xe}px`),c.push(p),m=k}return p&&y&&(p.textContent=E),c}_applyMinimumContrast(e,t,i,s,r,n){if(this._optionsService.rawOptions.minimumContrastRatio===1||oh(s.getCode()))return!1;let o=this._getContrastCache(s),h;if(!r&&!n&&(h=o.getColor(t.rgba,i.rgba)),h===void 0){let l=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);h=N.ensureContrastRatio(r??t,n??i,l),o.setColor((r??t).rgba,(n??i).rgba,h??null)}return h?(this._addStyle(e,`color:${h.css}`),!0):!1}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){let i=this._selectionStart,s=this._selectionEnd;return!i||!s?!1:this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0]}};ss=K([w(1,Zr),w(2,he),w(3,Ie),w(4,je),w(5,Tt),w(6,ht)],ss);var hh=class{constructor(e=()=>new lh){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s=0;if(!t&&!i&&e.length===1&&(s=e.charCodeAt(0))<256){if(this._flat[s]!==-9999)return this._flat[s];let o=this._measure(e,0);return o>0&&(this._flat[s]=o),o}let r=e;t&&(r+="B"),i&&(r+="I");let n=this._holey.get(r);if(n===void 0){let o=0;t&&(o|=1),i&&(o|=2),n=this._measure(e,o),n>0&&this._holey.set(r,n)}return n}_measure(e,t){return this._canvasElements[t].measure(e)}},lh=class{constructor(){typeof OffscreenCanvas<"u"?(this._canvas=new OffscreenCanvas(1,1),this._ctx=dr(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=dr(this._canvas.getContext("2d")))}setFont(e,t,i,s){let r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}},ch=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1]){this.clear();return}let r=e.buffers.active.ydisp,n=t[1]-r,o=i[1]-r,h=Math.max(n,0),l=Math.min(o,e.rows-1);if(h>=e.rows||l<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=n,this.viewportEndRow=o,this.viewportCappedStartRow=h,this.viewportCappedEndRow=l,this.startCol=t[0],this.endCol=i[0]}isCellSelected(e,t,i){return this.hasSelection?(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol):!1}};function dh(){return new ch}var Ci="xterm-dom-renderer-owner-",pe="xterm-rows",Ht="xterm-fg-",ur="xterm-bg-",pt="xterm-focus",Wt="xterm-selection",Gt="xterm-cursor-blink-idle",uh=1,rs=class extends I{constructor(e,t,i,s,r,n,o,h,l,a,d,c,u,f){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=n,this._linkifier2=o,this._charSizeService=l,this._optionsService=a,this._bufferService=d,this._coreService=c,this._coreBrowserService=u,this._themeService=f,this._terminalClass=uh++,this._rowElements=[],this._selectionRenderModel=dh(),this.onRequestRedraw=this._register(new b).event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(pe),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add(Wt),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=ah(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(_=>this._injectCss(_))),this._injectCss(this._themeService.colors),this._rowFactory=h.createInstance(ss,document),this._element.classList.add(Ci+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(_=>this._handleLinkHover(_))),this._register(this._linkifier2.onHideLinkUnderline(_=>this._handleLinkLeave(_))),this._cursorBlinkStateManager=new _h(this._rowContainer,this._coreBrowserService),this._register(B(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register(q(()=>this._cursorBlinkStateManager.dispose())),this._register(q(()=>{this._element.classList.remove(Ci+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new hh,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let i of this._rowElements)i.style.width=`${this.dimensions.css.canvas.width}px`,i.style.height=`${this.dimensions.css.cell.height}px`,i.style.lineHeight=`${this.dimensions.css.cell.height}px`,i.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .${pe} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${pe} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${pe} .xterm-dim { color: ${N.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;let i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${pe}.${pt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${pe}.${pt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .${pe}.${pt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${pe}.${Gt} .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${Wt} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Wt} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Wt} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[n,o]of e.ansi.entries())t+=`${this._terminalSelector} .${Ht}${n} { color: ${o.css}; }${this._terminalSelector} .${Ht}${n}.xterm-dim { color: ${N.multiplyOpacity(o,.5).css}; }${this._terminalSelector} .${ur}${n} { background-color: ${o.css}; }`;t+=`${this._terminalSelector} .${Ht}257 { color: ${N.opaque(e.background).css}; }${this._terminalSelector} .${Ht}257.xterm-dim { color: ${N.multiplyOpacity(N.opaque(e.background),.5).css}; }${this._terminalSelector} .${ur}257 { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let i=this._rowElements.length;i<=t;i++){let s=this._document.createElement("div");this._rowContainer.appendChild(s),this._rowElements.push(s)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(pt),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(pt),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,i){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i),this.renderRows(0,this._bufferService.rows-1),!e||!t||(this._selectionRenderModel.update(this._terminal,e,t,i),!this._selectionRenderModel.hasSelection))return;let s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,n=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,h=this._document.createDocumentFragment();if(i){let l=e[0]>t[0];h.appendChild(this._createSelectionElement(n,l?t[0]:e[0],l?e[0]:t[0],o-n+1))}else{let l=s===n?e[0]:0,a=n===r?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(n,l,a));let d=o-n-1;if(h.appendChild(this._createSelectionElement(n+1,0,this._bufferService.cols,d)),n!==o){let c=r===o?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(o,0,c))}}this._selectionContainer.appendChild(h)}_createSelectionElement(e,t,i,s=1){let r=this._document.createElement("div"),n=t*this.dimensions.css.cell.width,o=this.dimensions.css.cell.width*(i-t);return n+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-n),r.style.height=`${s*this.dimensions.css.cell.height}px`,r.style.top=`${e*this.dimensions.css.cell.height}px`,r.style.left=`${n}px`,r.style.width=`${o}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren()}renderRows(e,t){let i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),n=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,h=this._optionsService.rawOptions.cursorInactiveStyle;for(let l=e;l<=t;l++){let a=l+i.ydisp,d=this._rowElements[l],c=i.lines.get(a);if(!d||!c)break;d.replaceChildren(...this._rowFactory.createRow(c,a,a===s,o,h,r,n,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${Ci}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,n){i<0&&(e=0),s<0&&(t=0);let o=this._bufferService.rows-1;i=Math.max(Math.min(i,o),0),s=Math.max(Math.min(s,o),0),r=Math.min(r,this._bufferService.cols);let h=this._bufferService.buffer,l=h.ybase+h.y,a=Math.min(h.x,r-1),d=this._optionsService.rawOptions.cursorBlink,c=this._optionsService.rawOptions.cursorStyle,u=this._optionsService.rawOptions.cursorInactiveStyle;for(let f=i;f<=s;++f){let _=f+h.ydisp,p=this._rowElements[f],y=h.lines.get(_);if(!p||!y)break;p.replaceChildren(...this._rowFactory.createRow(y,_,_===l,c,u,a,d,this.dimensions.css.cell.width,this._widthCache,n?f===i?e:0:-1,n?(f===s?t:r)-1:-1))}}};rs=K([w(7,Ss),w(8,hi),w(9,he),w(10,ce),w(11,je),w(12,Ie),w(13,ht)],rs);var _h=class{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove(Gt),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove(Gt),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add(Gt),this._isIdlePaused=!0,this._idleTimeout=void 0}},ns=class extends I{constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new b),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new ph(this._optionsService))}catch{this._measureStrategy=this._register(new fh(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};ns=K([w(2,he)],ns);var Bn=class extends I{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},fh=class extends Bn{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},ph=class extends Bn{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}},gh=class extends I{constructor(e,t,i){super(),this._textarea=e,this._window=t,this.mainDocument=i,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new vh(this._window)),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new b),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange(s=>this._screenDprMonitor.setWindow(s))),this._register(_e.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(B(this._textarea,"focus",()=>this._isFocused=!0)),this._register(B(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},vh=class extends I{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new ot),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(q(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=B(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}},mh=class extends I{constructor(){super(),this.linkProviders=[],this._register(q(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function Ms(e,t,i){let s=i.getBoundingClientRect(),r=e.getComputedStyle(i),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-s.left-n,t.clientY-s.top-o]}function Sh(e,t,i,s,r,n,o,h,l){if(!n)return;let a=Ms(e,t,i);if(a)return a[0]=Math.ceil((a[0]+(l?o/2:0))/o),a[1]=Math.ceil(a[1]/h),a[0]=Math.min(Math.max(a[0],1),s+(l?1:0)),a[1]=Math.min(Math.max(a[1],1),r),a}var os=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,i,s,r){return Sh(window,e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){let i=Ms(be(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};os=K([w(0,Ne),w(1,hi)],os);var wh=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh())),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,e):e,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,t):t,!this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}},Dn={};eo(Dn,{getSafariVersion:()=>yh,isChromeOS:()=>Pn,isFirefox:()=>Rn,isLegacyEdge:()=>bh,isLinux:()=>Ps,isMac:()=>Dt,isNode:()=>ci,isSafari:()=>Mn,isWindows:()=>Ln});var ci=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),At=ci?"node":navigator.userAgent,Ls=ci?"node":navigator.platform,Rn=At.includes("Firefox"),bh=At.includes("Edge"),Mn=/^((?!chrome|android).)*safari/i.test(At);function yh(){if(!Mn)return 0;let e=At.match(/Version\/(\d+)/);return e===null||e.length<2?0:parseInt(e[1])}var Dt=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(Ls),Ln=["Windows","Win16","Win32","WinCE"].includes(Ls),Ps=Ls.indexOf("Linux")>=0,Pn=/\bCrOS\b/.test(At),Tn=class{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ir){s-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=r}this.clear()}},Ch=class extends Tn{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},kh=class extends Tn{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},oi=!ci&&"requestIdleCallback"in window?kh:Ch,xh=class{constructor(){this._queue=new oi}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}},as=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._rowCount=e,this._optionsService=i,this._charSizeService=s,this._coreService=r,this._coreBrowserService=h,this._renderer=this._register(new ot),this._pausedResizeTask=new xh,this._observerDisposable=this._register(new ot),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new b),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new b),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new wh((a,d)=>this._renderRows(a,d),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new Eh(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(q(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(o.onResize(()=>this._fullRefresh())),this._register(o.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(n.onDecorationRegistered(()=>this._fullRefresh())),this._register(n.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(o.cols,o.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(o.buffer.y,o.buffer.y,void 0,!0))),this._register(l.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(a=>this._registerIntersectionObserver(a,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){let i=new e.IntersectionObserver(s=>this._handleIntersectionChange(s[s.length-1]),{threshold:0});i.observe(t),this._observerDisposable.value=q(()=>i.disconnect())}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(t=>this.refreshRows(t.start,t.end,t.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};as=K([w(2,he),w(3,hi),w(4,je),w(5,Tt),w(6,ce),w(7,Ie),w(8,ht)],as);var Eh=class{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??(this._timeout=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3))}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Bh(e,t,i,s){let r=i.buffer.x,n=i.buffer.y;if(!i.buffer.hasScrollback)return Mh(r,n,e,t,i,s)+di(n,t,i,s)+Lh(r,n,e,t,i,s);let o;if(n===t)return o=r>e?"D":"C",Mt(Math.abs(r-e),Rt(o,s));o=n>t?"D":"C";let h=Math.abs(n-t),l=Rh(n>t?e:r,i)+(h-1)*i.cols+1+Dh(n>t?r:e,i);return Mt(l,Rt(o,s))}function Dh(e,t){return e-1}function Rh(e,t){return t.cols-e}function Mh(e,t,i,s,r,n){return di(t,s,r,n).length===0?"":Mt(On(e,t,e,t-it(t,r),!1,r).length,Rt("D",n))}function di(e,t,i,s){let r=e-it(e,i),n=t-it(t,i),o=Math.abs(r-n)-Ph(e,t,i);return Mt(o,Rt(An(e,t),s))}function Lh(e,t,i,s,r,n){let o;di(t,s,r,n).length>0?o=s-it(s,r):o=t;let h=s,l=Th(e,t,i,s,r,n);return Mt(On(e,o,i,h,l==="C",r).length,Rt(l,n))}function Ph(e,t,i){let s=0,r=e-it(e,i),n=t-it(t,i);for(let o=0;o=0&&e0?o=s-it(s,r):o=t,e=i&&ot?"A":"B"}function On(e,t,i,s,r,n){let o=e,h=t,l="";for(;(o!==i||h!==s)&&h>=0&&hn.cols-1?(l+=n.buffer.translateBufferLineToString(h,!1,e,o),o=0,e=0,h++):!r&&o<0&&(l+=n.buffer.translateBufferLineToString(h,!1,0,e+1),o=n.cols-1,e=o,h--);return l+n.buffer.translateBufferLineToString(h,!1,e,o)}function Rt(e,t){return"\x1B"+(t?"O":"[")+e}function Mt(e,t){e=Math.floor(e);let i="";for(let s=0;sthis._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let e=this.selectionStart,t=this.selectionEnd;return!e||!t?!1:e[1]>t[1]||e[1]===t[1]&&e[0]>t[0]}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function _r(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}var ki=50,Oh=15,Ih=50,Nh=500,Fh="\xA0",Hh=new RegExp(Fh,"g"),hs=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._element=e,this._screenElement=t,this._linkifier=i,this._bufferService=s,this._coreService=r,this._mouseService=n,this._optionsService=o,this._renderService=h,this._coreBrowserService=l,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new ke,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new b),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new b),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=a=>this._handleMouseMove(a),this._mouseUpListener=a=>this._handleMouseUp(a),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(a=>this._handleTrim(a)),this._register(this._bufferService.buffers.onBufferActivate(a=>this._handleBufferActivate(a))),this.enable(),this._model=new Ah(this._bufferService),this._activeSelectionMode=0,this._register(q(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(a=>{a.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";let i=this._bufferService.buffer,s=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return"";let r=e[0]r.replace(Hh," ")).join(Ln?`\r +`).trim(),l=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(h);Vt.register(o,l?.[2]??h,o)}return o}),this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(this._size===1){this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),this._size=0;return}let t=this._listeners,i=t.indexOf(e);if(i===-1)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,t[i]=void 0;let s=this._deliveryQueue.current===this;if(this._size*Uo<=t.length){let r=0;for(let n=0;n0}},Vo=class{constructor(){this.i=-1,this.end=0}enqueue(e,t,i){this.i=0,this.end=i,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},Xi=class{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new se,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new se,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}getZoomLevel(t){return this.mapWindowIdToZoomLevel.get(this.getWindowId(t))??0}setZoomLevel(t,i){if(this.getZoomLevel(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToZoomLevel.set(s,t),this._onDidChangeZoomLevel.fire(s)}getZoomFactor(t){return this.mapWindowIdToZoomFactor.get(this.getWindowId(t))??1}setZoomFactor(t,i){this.mapWindowIdToZoomFactor.set(this.getWindowId(i),t)}setFullscreen(t,i){if(this.isFullscreen(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToFullScreen.set(s,t),this._onDidChangeFullscreen.fire(s)}isFullscreen(t){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(t))}getWindowId(t){return t.vscodeWindowId}};Xi.INSTANCE=new Xi;var Es=Xi;function Yo(e,t,i){typeof t=="string"&&(t=e.matchMedia(t)),t.addEventListener("change",i)}var Vl=Es.INSTANCE.onDidChangeZoomLevel;function jo(e){return Es.INSTANCE.getZoomFactor(e)}var Yl=Es.INSTANCE.onDidChangeFullscreen,ct=typeof navigator=="object"?navigator.userAgent:"",Gi=ct.indexOf("Firefox")>=0,Yt=ct.indexOf("AppleWebKit")>=0,Bs=ct.indexOf("Chrome")>=0,fn=!Bs&&ct.indexOf("Safari")>=0,jl=ct.indexOf("Electron/")>=0,Xl=ct.indexOf("Android")>=0,jt=!1;if(typeof me.matchMedia=="function"){let e=me.matchMedia("(display-mode: standalone) or (display-mode: window-controls-overlay)"),t=me.matchMedia("(display-mode: fullscreen)");jt=e.matches,Yo(me,e,({matches:i})=>{jt&&t.matches||(jt=i)})}function Xo(){return jt}var ot="en",si=!1,ri=!1,Bt=!1,Go=!1,pn=!1,gn=!1,Jo=!1,Zo=!1,Qo=!1,ea=!1,Ht,Xt=ot,Gs=ot,ta,Ae,Ie=globalThis,ve;typeof Ie.vscode<"u"&&typeof Ie.vscode.process<"u"?ve=Ie.vscode.process:typeof process<"u"&&typeof process?.versions?.node=="string"&&(ve=process);var vn=typeof ve?.versions?.electron=="string",ia=vn&&ve?.type==="renderer";if(typeof ve=="object"){si=ve.platform==="win32",ri=ve.platform==="darwin",Bt=ve.platform==="linux",Go=Bt&&!!ve.env.SNAP&&!!ve.env.SNAP_REVISION,Jo=vn,Qo=!!ve.env.CI||!!ve.env.BUILD_ARTIFACTSTAGINGDIRECTORY,Ht=ot,Xt=ot;let e=ve.env.VSCODE_NLS_CONFIG;if(e)try{let t=JSON.parse(e);Ht=t.userLocale,Gs=t.osLocale,Xt=t.resolvedLanguage||ot,ta=t.languagePack?.translationsConfigFile}catch{}pn=!0}else typeof navigator=="object"&&!ia?(Ae=navigator.userAgent,si=Ae.indexOf("Windows")>=0,ri=Ae.indexOf("Macintosh")>=0,Zo=(Ae.indexOf("Macintosh")>=0||Ae.indexOf("iPad")>=0||Ae.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,Bt=Ae.indexOf("Linux")>=0,ea=Ae?.indexOf("Mobi")>=0,gn=!0,Xt=globalThis._VSCODE_NLS_LANGUAGE||ot,Ht=navigator.language.toLowerCase(),Gs=Ht):console.error("Unable to resolve platform.");var mi=0;ri?mi=1:si?mi=3:Bt&&(mi=2);var mn=si,Re=ri,sa=Bt,Si=pn,ra=gn&&typeof Ie.importScripts=="function",Gl=ra?Ie.origin:void 0,Me=Ae,$e=Xt,na;(e=>{function t(){return $e}e.value=t;function i(){return $e.length===2?$e==="en":$e.length>=3?$e[0]==="e"&&$e[1]==="n"&&$e[2]==="-":!1}e.isDefaultVariant=i;function s(){return $e==="en"}e.isDefault=s})(na||(na={}));var oa=typeof Ie.postMessage=="function"&&!Ie.importScripts,aa=(()=>{if(oa){let e=[];Ie.addEventListener("message",i=>{if(i.data&&i.data.vscodeScheduleAsyncWork)for(let s=0,r=e.length;s{let s=++t;e.push({id:s,callback:i}),Ie.postMessage({vscodeScheduleAsyncWork:s},"*")}}return e=>setTimeout(e)})(),ha=!!(Me&&Me.indexOf("Chrome")>=0),Jl=!!(Me&&Me.indexOf("Firefox")>=0),Zl=!!(!ha&&Me&&Me.indexOf("Safari")>=0),Ql=!!(Me&&Me.indexOf("Edg/")>=0),ec=!!(Me&&Me.indexOf("Android")>=0),Ue=typeof navigator=="object"?navigator:{},tc={clipboard:{writeText:Si||document.queryCommandSupported&&document.queryCommandSupported("copy")||!!(Ue&&Ue.clipboard&&Ue.clipboard.writeText),readText:Si||!!(Ue&&Ue.clipboard&&Ue.clipboard.readText)},keyboard:Si||Xo()?0:Ue.keyboard||fn?1:2,touch:"ontouchstart"in me||Ue.maxTouchPoints>0,pointerEvents:me.PointerEvent&&("ontouchstart"in me||navigator.maxTouchPoints>0)},Ds=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||0}},wi=new Ds,Js=new Ds,Zs=new Ds,la=new Array(230),Sn;(e=>{function t(h){return wi.keyCodeToStr(h)}e.toString=t;function i(h){return wi.strToKeyCode(h)}e.fromString=i;function s(h){return Js.keyCodeToStr(h)}e.toUserSettingsUS=s;function r(h){return Zs.keyCodeToStr(h)}e.toUserSettingsGeneral=r;function n(h){return Js.strToKeyCode(h)||Zs.strToKeyCode(h)}e.fromUserSettings=n;function o(h){if(h>=98&&h<=113)return null;switch(h){case 16:return"Up";case 18:return"Down";case 15:return"Left";case 17:return"Right"}return wi.keyCodeToStr(h)}e.toElectronAccelerator=o})(Sn||(Sn={}));var ca=class wn{constructor(t,i,s,r,n){this.ctrlKey=t,this.shiftKey=i,this.altKey=s,this.metaKey=r,this.keyCode=n}equals(t){return t instanceof wn&&this.ctrlKey===t.ctrlKey&&this.shiftKey===t.shiftKey&&this.altKey===t.altKey&&this.metaKey===t.metaKey&&this.keyCode===t.keyCode}getHashCode(){let t=this.ctrlKey?"1":"0",i=this.shiftKey?"1":"0",s=this.altKey?"1":"0",r=this.metaKey?"1":"0";return`K${t}${i}${s}${r}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new da([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}},da=class{constructor(e){if(e.length===0)throw yo("chords");this.chords=e}getHashCode(){let e="";for(let t=0,i=this.chords.length;t{function t(i){return i===e.None||i===e.Cancelled||i instanceof wa?!0:!i||typeof i!="object"?!1:typeof i.isCancellationRequested=="boolean"&&typeof i.onCancellationRequested=="function"}e.isCancellationToken=t,e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:di.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:bn})})(Sa||(Sa={}));var wa=class{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?bn:(this._emitter||(this._emitter=new se),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}};var Rs=class{constructor(e,t){this._isDisposed=!1,this._token=-1,typeof e=="function"&&typeof t=="number"&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new zi("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new zi("Calling 'setIfNotSet' on a disposed TimeoutTimer");this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},ba=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this.isDisposed)throw new zi("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();let s=i.setInterval(()=>{e()},t);this.disposable=je(()=>{i.clearInterval(s),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}},ya,bi;(function(){typeof globalThis.requestIdleCallback!="function"||typeof globalThis.cancelIdleCallback!="function"?bi=(e,t)=>{aa(()=>{if(i)return;let s=Date.now()+15;t(Object.freeze({didTimeout:!0,timeRemaining(){return Math.max(0,s-Date.now())}}))});let i=!1;return{dispose(){i||(i=!0)}}}:bi=(e,t,i)=>{let s=e.requestIdleCallback(t,typeof i=="number"?{timeout:i}:void 0),r=!1;return{dispose(){r||(r=!0,e.cancelIdleCallback(s))}}},ya=e=>bi(globalThis,e)})();var Ca;(e=>{async function t(s){let r,n=await Promise.all(s.map(o=>o.then(h=>h,h=>{r||(r=h)})));if(typeof r<"u")throw r;return n}e.settled=t;function i(s){return new Promise(async(r,n)=>{try{await s(r,n)}catch(o){n(o)}})}e.withAsyncBody=i})(Ca||(Ca={}));var tr=class we{static fromArray(t){return new we(i=>{i.emitMany(t)})}static fromPromise(t){return new we(async i=>{i.emitMany(await t)})}static fromPromises(t){return new we(async i=>{await Promise.all(t.map(async s=>i.emitOne(await s)))})}static merge(t){return new we(async i=>{await Promise.all(t.map(async s=>{for await(let r of s)i.emitOne(r)}))})}constructor(t,i){this._state=0,this._results=[],this._error=null,this._onReturn=i,this._onStateChanged=new se,queueMicrotask(async()=>{let s={emitOne:r=>this.emitOne(r),emitMany:r=>this.emitMany(r),reject:r=>this.reject(r)};try{await Promise.resolve(t(s)),this.resolve()}catch(r){this.reject(r)}finally{s.emitOne=void 0,s.emitMany=void 0,s.reject=void 0}})}[Symbol.asyncIterator](){let t=0;return{next:async()=>{do{if(this._state===2)throw this._error;if(t(this._onReturn?.(),{done:!0,value:void 0})}}static map(t,i){return new we(async s=>{for await(let r of t)s.emitOne(i(r))})}map(t){return we.map(this,t)}static filter(t,i){return new we(async s=>{for await(let r of t)i(r)&&s.emitOne(r)})}filter(t){return we.filter(this,t)}static coalesce(t){return we.filter(t,i=>!!i)}coalesce(){return we.coalesce(this)}static async toPromise(t){let i=[];for await(let s of t)i.push(s);return i}toPromise(){return we.toPromise(this)}emitOne(t){this._state===0&&(this._results.push(t),this._onStateChanged.fire())}emitMany(t){this._state===0&&(this._results=this._results.concat(t),this._onStateChanged.fire())}resolve(){this._state===0&&(this._state=1,this._onStateChanged.fire())}reject(t){this._state===0&&(this._state=2,this._error=t,this._onStateChanged.fire())}};tr.EMPTY=tr.fromArray([]);function ka(e){return 55296<=e&&e<=56319}function ir(e){return 56320<=e&&e<=57343}function xa(e,t){return(e-55296<<10)+(t-56320)+65536}function Ea(e){return Ms(e,0)}function Ms(e,t){switch(typeof e){case"object":return e===null?Oe(349,t):Array.isArray(e)?Da(e,t):Ra(e,t);case"string":return yn(e,t);case"boolean":return Ba(e,t);case"number":return Oe(e,t);case"undefined":return Oe(937,t);default:return Oe(617,t)}}function Oe(e,t){return(t<<5)-t+e|0}function Ba(e,t){return Oe(e?433:863,t)}function yn(e,t){t=Oe(149417,t);for(let i=0,s=e.length;iMs(s,i),t)}function Ra(e,t){return t=Oe(181387,t),Object.keys(e).sort().reduce((i,s)=>(i=yn(s,i),Ms(e[s],i)),t)}function yi(e,t,i=32){let s=i-t,r=~((1<>>s)>>>0}function sr(e,t=0,i=e.byteLength,s=0){for(let r=0;ri.toString(16).padStart(2,"0")).join(""):Ma((e>>>0).toString(16),t/4)}var La=class Cn{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(67),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(t){let i=t.length;if(i===0)return;let s=this._buff,r=this._buffLen,n=this._leftoverHighSurrogate,o,h;for(n!==0?(o=n,h=-1,n=0):(o=t.charCodeAt(0),h=0);;){let l=o;if(ka(o))if(h+1>>6,t[i++]=128|(s&63)>>>0):s<65536?(t[i++]=224|(s&61440)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0):(t[i++]=240|(s&1835008)>>>18,t[i++]=128|(s&258048)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0),i>=64&&(this._step(),i-=64,this._totalLen+=64,t[0]=t[64],t[1]=t[65],t[2]=t[66]),i}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),ft(this._h0)+ft(this._h1)+ft(this._h2)+ft(this._h3)+ft(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,sr(this._buff,this._buffLen),this._buffLen>56&&(this._step(),sr(this._buff));let t=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(t/4294967296),!1),this._buffDV.setUint32(60,t%4294967296,!1),this._step()}_step(){let t=Cn._bigBlock32,i=this._buffDV;for(let c=0;c<64;c+=4)t.setUint32(c,i.getUint32(c,!1),!1);for(let c=64;c<320;c+=4)t.setUint32(c,yi(t.getUint32(c-12,!1)^t.getUint32(c-32,!1)^t.getUint32(c-56,!1)^t.getUint32(c-64,!1),1),!1);let s=this._h0,r=this._h1,n=this._h2,o=this._h3,h=this._h4,l,a,d;for(let c=0;c<80;c++)c<20?(l=r&n|~r&o,a=1518500249):c<40?(l=r^n^o,a=1859775393):c<60?(l=r&n|r&o|n&o,a=2400959708):(l=r^n^o,a=3395469782),d=yi(s,5)+l+h+a+t.getUint32(c*4,!1)&4294967295,h=o,o=n,n=yi(r,30),r=s,s=d;this._h0=this._h0+s&4294967295,this._h1=this._h1+r&4294967295,this._h2=this._h2+n&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+h&4294967295}};La._bigBlock32=new DataView(new ArrayBuffer(320));var{registerWindow:ic,getWindow:be,getDocument:sc,getWindows:rc,getWindowsCount:nc,getWindowId:rr,getWindowById:oc,hasWindow:ac,onDidRegisterWindow:Pa,onWillUnregisterWindow:hc,onDidUnregisterWindow:lc}=(function(){let e=new Map,t={window:me,disposables:new tt};e.set(me.vscodeWindowId,t);let i=new se,s=new se,r=new se;function n(o,h){return(typeof o=="number"?e.get(o):void 0)??(h?t:void 0)}return{onDidRegisterWindow:i.event,onWillUnregisterWindow:r.event,onDidUnregisterWindow:s.event,registerWindow(o){if(e.has(o.vscodeWindowId))return Se.None;let h=new tt,l={window:o,disposables:h.add(new tt)};return e.set(o.vscodeWindowId,l),h.add(je(()=>{e.delete(o.vscodeWindowId),s.fire(o)})),h.add(B(o,Z.BEFORE_UNLOAD,()=>{r.fire(o)})),i.fire(l),h},getWindows(){return e.values()},getWindowsCount(){return e.size},getWindowId(o){return o.vscodeWindowId},hasWindow(o){return e.has(o)},getWindowById:n,getWindow(o){let h=o;if(h?.ownerDocument?.defaultView)return h.ownerDocument.defaultView.window;let l=o;return l?.view?l.view.window:me},getDocument(o){return be(o).document}}})(),Ta=class{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}};function B(e,t,i,s){return new Ta(e,t,i,s)}function Aa(e,t){return function(i){return t(new bt(e,i))}}function Oa(e){return function(t){return e(new Ji(t))}}var nr=function(e,t,i,s){let r=i;return t==="click"||t==="mousedown"||t==="contextmenu"?r=Aa(be(e),i):(t==="keydown"||t==="keypress"||t==="keyup")&&(r=Oa(i)),B(e,t,r,s)},Ia,ni,Na=class extends ba{constructor(e){super(),this.defaultTarget=e&&be(e)}cancelAndSet(e,t,i){return super.cancelAndSet(e,t,i??this.defaultTarget)}},Ci=class{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){Ut(e)}}static sort(e,t){return t.priority-e.priority}};(function(){let e=new Map,t=new Map,i=new Map,s=new Map,r=n=>{i.set(n,!1);let o=e.get(n)??[];for(t.set(n,o),e.set(n,[]),s.set(n,!0);o.length>0;)o.sort(Ci.sort),o.shift().execute();s.set(n,!1)};ni=(n,o,h=0)=>{let l=rr(n),a=new Ci(o,h),d=e.get(l);return d||(d=[],e.set(l,d)),d.push(a),i.get(l)||(i.set(l,!0),n.requestAnimationFrame(()=>r(l))),a},Ia=(n,o,h)=>{let l=rr(n);if(s.get(l)){let a=new Ci(o,h),d=t.get(l);return d||(d=[],t.set(l,d)),d.push(a),a}else return ni(n,o,h)}})();var or=class Gt{constructor(t,i){this.width=t,this.height=i}with(t=this.width,i=this.height){return t!==this.width||i!==this.height?new Gt(t,i):this}static is(t){return typeof t=="object"&&typeof t.height=="number"&&typeof t.width=="number"}static lift(t){return t instanceof Gt?t:new Gt(t.width,t.height)}static equals(t,i){return t===i?!0:!t||!i?!1:t.width===i.width&&t.height===i.height}};or.None=new or(0,0);function Fa(e){let t=e.getBoundingClientRect(),i=be(e);return{left:t.left+i.scrollX,top:t.top+i.scrollY,width:t.width,height:t.height}}var cc=new class{constructor(){this.mutationObservers=new Map}observe(e,t,i){let s=this.mutationObservers.get(e);s||(s=new Map,this.mutationObservers.set(e,s));let r=Ea(i),n=s.get(r);if(n)n.users+=1;else{let o=new se,h=new MutationObserver(a=>o.fire(a));h.observe(e,i);let l=n={users:1,observer:h,onDidMutate:o.event};t.add(je(()=>{l.users-=1,l.users===0&&(o.dispose(),h.disconnect(),s?.delete(r),s?.size===0&&this.mutationObservers.delete(e))})),s.set(r,n)}return n.onDidMutate}},Z={CLICK:"click",AUXCLICK:"auxclick",DBLCLICK:"dblclick",MOUSE_UP:"mouseup",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_MOVE:"mousemove",MOUSE_OUT:"mouseout",MOUSE_ENTER:"mouseenter",MOUSE_LEAVE:"mouseleave",MOUSE_WHEEL:"wheel",POINTER_UP:"pointerup",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_LEAVE:"pointerleave",CONTEXT_MENU:"contextmenu",WHEEL:"wheel",KEY_DOWN:"keydown",KEY_PRESS:"keypress",KEY_UP:"keyup",LOAD:"load",BEFORE_UNLOAD:"beforeunload",UNLOAD:"unload",PAGE_SHOW:"pageshow",PAGE_HIDE:"pagehide",PASTE:"paste",ABORT:"abort",ERROR:"error",RESIZE:"resize",SCROLL:"scroll",FULLSCREEN_CHANGE:"fullscreenchange",WK_FULLSCREEN_CHANGE:"webkitfullscreenchange",SELECT:"select",CHANGE:"change",SUBMIT:"submit",RESET:"reset",FOCUS:"focus",FOCUS_IN:"focusin",FOCUS_OUT:"focusout",BLUR:"blur",INPUT:"input",STORAGE:"storage",DRAG_START:"dragstart",DRAG:"drag",DRAG_ENTER:"dragenter",DRAG_LEAVE:"dragleave",DRAG_OVER:"dragover",DROP:"drop",DRAG_END:"dragend",ANIMATION_START:Yt?"webkitAnimationStart":"animationstart",ANIMATION_END:Yt?"webkitAnimationEnd":"animationend",ANIMATION_ITERATION:Yt?"webkitAnimationIteration":"animationiteration"},Ha=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function kn(e,t,i,...s){let r=Ha.exec(t);if(!r)throw new Error("Bad use of emmet");let n=r[1]||"div",o;return e!=="http://www.w3.org/1999/xhtml"?o=document.createElementNS(e,n):o=document.createElement(n),r[3]&&(o.id=r[3]),r[4]&&(o.className=r[4].replace(/\./g," ").trim()),i&&Object.entries(i).forEach(([h,l])=>{typeof l>"u"||(/^on\w+$/.test(h)?o[h]=l:h==="selected"?l&&o.setAttribute(h,"true"):o.setAttribute(h,l))}),o.append(...s),o}function Wa(e,t,...i){return kn("http://www.w3.org/1999/xhtml",e,t,...i)}Wa.SVG=function(e,t,...i){return kn("http://www.w3.org/2000/svg",e,t,...i)};var za=class{constructor(e){this.domNode=e,this._maxWidth="",this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._paddingTop="",this._paddingLeft="",this._paddingBottom="",this._paddingRight="",this._fontFamily="",this._fontWeight="",this._fontSize="",this._fontStyle="",this._fontFeatureSettings="",this._fontVariationSettings="",this._textDecoration="",this._lineHeight="",this._letterSpacing="",this._className="",this._display="",this._position="",this._visibility="",this._color="",this._backgroundColor="",this._layerHint=!1,this._contain="none",this._boxShadow=""}setMaxWidth(e){let t=de(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){let t=de(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){let t=de(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){let t=de(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){let t=de(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){let t=de(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){let t=de(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){let t=de(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){let t=de(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){let t=de(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){let t=de(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){let t=de(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){let t=de(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){let t=de(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?"translate3d(0px, 0px, 0px)":"")}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}};function de(e){return typeof e=="number"?`${e}px`:e}function Dt(e){return new za(e)}var xn=class{constructor(){this._hooks=new tt,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let i=this._onStopCallback;this._onStopCallback=null,e&&i&&i(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,i,s,r){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=s,this._onStopCallback=r;let n=e;try{e.setPointerCapture(t),this._hooks.add(je(()=>{try{e.releasePointerCapture(t)}catch{}}))}catch{n=be(e)}this._hooks.add(B(n,Z.POINTER_MOVE,o=>{if(o.buttons!==i){this.stopMonitoring(!0);return}o.preventDefault(),this._pointerMoveCallback(o)})),this._hooks.add(B(n,Z.POINTER_UP,o=>this.stopMonitoring(!0)))}};function Ka(e,t,i){let s=null,r=null;if(typeof i.value=="function"?(s="value",r=i.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof i.get=="function"&&(s="get",r=i.get),!r)throw new Error("not supported");let n=`$memoize$${t}`;i[s]=function(...o){return this.hasOwnProperty(n)||Object.defineProperty(this,n,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,o)}),this[n]}}var Ce;(e=>(e.Tap="-xterm-gesturetap",e.Change="-xterm-gesturechange",e.Start="-xterm-gesturestart",e.End="-xterm-gesturesend",e.Contextmenu="-xterm-gesturecontextmenu"))(Ce||(Ce={}));var yt=class ae extends Se{constructor(){super(),this.dispatched=!1,this.targets=new Ys,this.ignoreTargets=new Ys,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(di.runAndSubscribe(Pa,({window:t,disposables:i})=>{i.add(B(t.document,"touchstart",s=>this.onTouchStart(s),{passive:!1})),i.add(B(t.document,"touchend",s=>this.onTouchEnd(t,s))),i.add(B(t.document,"touchmove",s=>this.onTouchMove(s),{passive:!1}))},{window:me,disposables:this._store}))}static addTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Vs(new ae));let i=ae.INSTANCE.targets.push(t);return je(i)}static ignoreTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Vs(new ae));let i=ae.INSTANCE.ignoreTargets.push(t);return je(i)}static isTouchDevice(){return"ontouchstart"in me||navigator.maxTouchPoints>0}dispose(){this.handle&&(this.handle.dispose(),this.handle=null),super.dispose()}onTouchStart(t){let i=Date.now();this.handle&&(this.handle.dispose(),this.handle=null);for(let s=0,r=t.targetTouches.length;s=ae.HOLD_DELAY&&Math.abs(l.initialPageX-fe(l.rollingPageX))<30&&Math.abs(l.initialPageY-fe(l.rollingPageY))<30){let d=this.newGestureEvent(Ce.Contextmenu,l.initialTarget);d.pageX=fe(l.rollingPageX),d.pageY=fe(l.rollingPageY),this.dispatchEvent(d)}else if(r===1){let d=fe(l.rollingPageX),c=fe(l.rollingPageY),u=fe(l.rollingTimestamps)-l.rollingTimestamps[0],f=d-l.rollingPageX[0],_=c-l.rollingPageY[0],p=[...this.targets].filter(y=>l.initialTarget instanceof Node&&y.contains(l.initialTarget));this.inertia(t,p,s,Math.abs(f)/u,f>0?1:-1,d,Math.abs(_)/u,_>0?1:-1,c)}this.dispatchEvent(this.newGestureEvent(Ce.End,l.initialTarget)),delete this.activeTouches[h.identifier]}this.dispatched&&(i.preventDefault(),i.stopPropagation(),this.dispatched=!1)}newGestureEvent(t,i){let s=document.createEvent("CustomEvent");return s.initEvent(t,!1,!0),s.initialTarget=i,s.tapCount=0,s}dispatchEvent(t){if(t.type===Ce.Tap){let i=new Date().getTime(),s=0;i-this._lastSetTapCountTime>ae.CLEAR_TAP_COUNT_TIME?s=1:s=2,this._lastSetTapCountTime=i,t.tapCount=s}else(t.type===Ce.Change||t.type===Ce.Contextmenu)&&(this._lastSetTapCountTime=0);if(t.initialTarget instanceof Node){for(let s of this.ignoreTargets)if(s.contains(t.initialTarget))return;let i=[];for(let s of this.targets)if(s.contains(t.initialTarget)){let r=0,n=t.initialTarget;for(;n&&n!==s;)r++,n=n.parentElement;i.push([r,s])}i.sort((s,r)=>s[0]-r[0]);for(let[s,r]of i)r.dispatchEvent(t),this.dispatched=!0}}inertia(t,i,s,r,n,o,h,l,a){this.handle=ni(t,()=>{let d=Date.now(),c=d-s,u=0,f=0,_=!0;r+=ae.SCROLL_FRICTION*c,h+=ae.SCROLL_FRICTION*c,r>0&&(_=!1,u=n*r*c),h>0&&(_=!1,f=l*h*c);let p=this.newGestureEvent(Ce.Change);p.translationX=u,p.translationY=f,i.forEach(y=>y.dispatchEvent(p)),_||this.inertia(t,i,d,r,n,o+u,h,l,a+f)})}onTouchMove(t){let i=Date.now();for(let s=0,r=t.changedTouches.length;s3&&(o.rollingPageX.shift(),o.rollingPageY.shift(),o.rollingTimestamps.shift()),o.rollingPageX.push(n.pageX),o.rollingPageY.push(n.pageY),o.rollingTimestamps.push(i)}this.dispatched&&(t.preventDefault(),t.stopPropagation(),this.dispatched=!1)}};yt.SCROLL_FRICTION=-.005,yt.HOLD_DELAY=700,yt.CLEAR_TAP_COUNT_TIME=400,K([Ka],yt,"isTouchDevice",1);var En=yt,Ls=class extends Se{onclick(e,t){this._register(B(e,Z.CLICK,i=>t(new bt(be(e),i))))}onmousedown(e,t){this._register(B(e,Z.MOUSE_DOWN,i=>t(new bt(be(e),i))))}onmouseover(e,t){this._register(B(e,Z.MOUSE_OVER,i=>t(new bt(be(e),i))))}onmouseleave(e,t){this._register(B(e,Z.MOUSE_LEAVE,i=>t(new bt(be(e),i))))}onkeydown(e,t){this._register(B(e,Z.KEY_DOWN,i=>t(new Ji(i))))}onkeyup(e,t){this._register(B(e,Z.KEY_UP,i=>t(new Ji(i))))}oninput(e,t){this._register(B(e,Z.INPUT,t))}onblur(e,t){this._register(B(e,Z.BLUR,t))}onfocus(e,t){this._register(B(e,Z.FOCUS,t))}onchange(e,t){this._register(B(e,Z.CHANGE,t))}ignoreGesture(e){return En.ignoreTarget(e)}},ar=11,$a=class extends Ls{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=e.bgWidth+"px",this.bgDomNode.style.height=e.bgHeight+"px",typeof e.top<"u"&&(this.bgDomNode.style.top="0px"),typeof e.left<"u"&&(this.bgDomNode.style.left="0px"),typeof e.bottom<"u"&&(this.bgDomNode.style.bottom="0px"),typeof e.right<"u"&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=e.className,this.domNode.style.position="absolute",this.domNode.style.width=ar+"px",this.domNode.style.height=ar+"px",typeof e.top<"u"&&(this.domNode.style.top=e.top+"px"),typeof e.left<"u"&&(this.domNode.style.left=e.left+"px"),typeof e.bottom<"u"&&(this.domNode.style.bottom=e.bottom+"px"),typeof e.right<"u"&&(this.domNode.style.right=e.right+"px"),this._pointerMoveMonitor=this._register(new xn),this._register(nr(this.bgDomNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._register(nr(this.domNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._pointerdownRepeatTimer=this._register(new Na),this._pointerdownScheduleRepeatTimer=this._register(new Rs)}_arrowPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,be(e))};this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(t,200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,i=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault()}},Ua=class Zi{constructor(t,i,s,r,n,o,h){this._forceIntegerValues=t,this._scrollStateBrand=void 0,this._forceIntegerValues&&(i=i|0,s=s|0,r=r|0,n=n|0,o=o|0,h=h|0),this.rawScrollLeft=r,this.rawScrollTop=h,i<0&&(i=0),r+i>s&&(r=s-i),r<0&&(r=0),n<0&&(n=0),h+n>o&&(h=o-n),h<0&&(h=0),this.width=i,this.scrollWidth=s,this.scrollLeft=r,this.height=n,this.scrollHeight=o,this.scrollTop=h}equals(t){return this.rawScrollLeft===t.rawScrollLeft&&this.rawScrollTop===t.rawScrollTop&&this.width===t.width&&this.scrollWidth===t.scrollWidth&&this.scrollLeft===t.scrollLeft&&this.height===t.height&&this.scrollHeight===t.scrollHeight&&this.scrollTop===t.scrollTop}withScrollDimensions(t,i){return new Zi(this._forceIntegerValues,typeof t.width<"u"?t.width:this.width,typeof t.scrollWidth<"u"?t.scrollWidth:this.scrollWidth,i?this.rawScrollLeft:this.scrollLeft,typeof t.height<"u"?t.height:this.height,typeof t.scrollHeight<"u"?t.scrollHeight:this.scrollHeight,i?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new Zi(this._forceIntegerValues,this.width,this.scrollWidth,typeof t.scrollLeft<"u"?t.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof t.scrollTop<"u"?t.scrollTop:this.rawScrollTop)}createScrollEvent(t,i){let s=this.width!==t.width,r=this.scrollWidth!==t.scrollWidth,n=this.scrollLeft!==t.scrollLeft,o=this.height!==t.height,h=this.scrollHeight!==t.scrollHeight,l=this.scrollTop!==t.scrollTop;return{inSmoothScrolling:i,oldWidth:t.width,oldScrollWidth:t.scrollWidth,oldScrollLeft:t.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:t.height,oldScrollHeight:t.scrollHeight,oldScrollTop:t.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:s,scrollWidthChanged:r,scrollLeftChanged:n,heightChanged:o,scrollHeightChanged:h,scrollTopChanged:l}}},qa=class extends Se{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new Ua(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let i=this._state.withScrollDimensions(e,t);this._setState(i,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>"u"?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>"u"?this._smoothScrolling.to.scrollTop:e.scrollTop};let i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;t?s=new lr(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):s=this._smoothScrolling.combine(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{let i=this._state.withScrollPosition(e);this._smoothScrolling=lr.start(this._state,i,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),!!this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}},hr=class{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}};function ki(e,t){let i=t-e;return function(s){return e+i*ja(s)}}function Va(e,t,i){return function(s){return s2.5*s){let r,n;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" fade":"")))}},Ga=140,Bn=class extends Ls{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new Xa(e.visibility,"visible scrollbar "+e.extraScrollbarClassName,"invisible scrollbar "+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new xn),this._shouldRender=!0,this.domNode=Dt(document.createElement("div")),this.domNode.setAttribute("role","presentation"),this.domNode.setAttribute("aria-hidden","true"),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition("absolute"),this._register(B(this.domNode.domNode,Z.POINTER_DOWN,t=>this._domNodePointerDown(t)))}_createArrow(e){let t=this._register(new $a(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,i,s){this.slider=Dt(document.createElement("div")),this.slider.setClassName("slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),typeof i=="number"&&this.slider.setWidth(i),typeof s=="number"&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(B(this.slider.domNode,Z.POINTER_DOWN,r=>{r.button===0&&(r.preventDefault(),this._sliderPointerDown(r))})),this.onclick(this.slider.domNode,r=>{r.leftButton&&r.stopPropagation()})}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){let t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?e.button===0&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,i;if(e.target===this.domNode.domNode&&typeof e.offsetX=="number"&&typeof e.offsetY=="number")t=e.offsetX,i=e.offsetY;else{let r=Fa(this.domNode.domNode);t=e.pageX-r.left,i=e.pageY-r.top}let s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,r=>{let n=this._sliderOrthogonalPointerPosition(r),o=Math.abs(n-i);if(mn&&o>Ga){this._setDesiredScrollPositionNow(s.getScrollPosition());return}let h=this._sliderPointerPosition(r)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(h))},()=>{this.slider.toggleClassName("active",!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(e){let t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}},Dn=class es{constructor(t,i,s,r,n,o){this._scrollbarSize=Math.round(i),this._oppositeScrollbarSize=Math.round(s),this._arrowSize=Math.round(t),this._visibleSize=r,this._scrollSize=n,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new es(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(t){let i=Math.round(t);return this._visibleSize!==i?(this._visibleSize=i,this._refreshComputedValues(),!0):!1}setScrollSize(t){let i=Math.round(t);return this._scrollSize!==i?(this._scrollSize=i,this._refreshComputedValues(),!0):!1}setScrollPosition(t){let i=Math.round(t);return this._scrollPosition!==i?(this._scrollPosition=i,this._refreshComputedValues(),!0):!1}setScrollbarSize(t){this._scrollbarSize=Math.round(t)}setOppositeScrollbarSize(t){this._oppositeScrollbarSize=Math.round(t)}static _computeValues(t,i,s,r,n){let o=Math.max(0,s-t),h=Math.max(0,o-2*i),l=r>0&&r>s;if(!l)return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(h),computedSliderRatio:0,computedSliderPosition:0};let a=Math.round(Math.max(20,Math.floor(s*h/r))),d=(h-a)/(r-s),c=n*d;return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(a),computedSliderRatio:d,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){let t=es._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=t.computedAvailableSize,this._computedIsNeeded=t.computedIsNeeded,this._computedSliderSize=t.computedSliderSize,this._computedSliderRatio=t.computedSliderRatio,this._computedSliderPosition=t.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize-this._computedSliderSize/2;return Math.round(i/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize,s=this._scrollPosition;return i0&&Math.abs(t.deltaY)>0)return 1;let s=.5;if((!this._isAlmostInt(t.deltaX)||!this._isAlmostInt(t.deltaY))&&(s+=.25),i){let r=Math.abs(t.deltaX),n=Math.abs(t.deltaY),o=Math.abs(i.deltaX),h=Math.abs(i.deltaY),l=Math.max(Math.min(r,o),1),a=Math.max(Math.min(n,h),1),d=Math.max(r,o),c=Math.max(n,h);d%l===0&&c%a===0&&(s-=.5)}return Math.min(Math.max(s,0),1)}_isAlmostInt(t){return Math.abs(Math.round(t)-t)<.01}};ts.INSTANCE=new ts;var th=ts,ih=class extends Ls{constructor(e,t,i){super(),this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new se),this.onWillScroll=this._onWillScroll.event,this._options=rh(t),this._scrollable=i,this._register(this._scrollable.onScroll(r=>{this._onWillScroll.fire(r),this._onDidScroll(r),this._onScroll.fire(r)}));let s={onMouseWheel:r=>this._onMouseWheel(r),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new Za(this._scrollable,this._options,s)),this._horizontalScrollbar=this._register(new Ja(this._scrollable,this._options,s)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=Dt(document.createElement("div")),this._leftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=Dt(document.createElement("div")),this._topShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=Dt(document.createElement("div")),this._topLeftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,r=>this._onMouseOver(r)),this.onmouseleave(this._listenOnDomNode,r=>this._onMouseLeave(r)),this._hideTimeout=this._register(new Rs),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=ii(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,Re&&(this._options.className+=" mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){typeof e.handleMouseWheel<"u"&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<"u"&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<"u"&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<"u"&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<"u"&&(this._options.horizontal=e.horizontal),typeof e.vertical<"u"&&(this._options.vertical=e.vertical),typeof e.horizontalScrollbarSize<"u"&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<"u"&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<"u"&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new er(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=ii(this._mouseWheelToDispose),e)){let t=i=>{this._onMouseWheel(new er(i))};this._mouseWheelToDispose.push(B(this._listenOnDomNode,Z.MOUSE_WHEEL,t,{passive:!1}))}}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=th.INSTANCE;dr&&t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let r=e.deltaY*this._options.mouseWheelScrollSensitivity,n=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&n+r===0?n=r=0:Math.abs(r)>=Math.abs(n)?n=0:r=0),this._options.flipAxes&&([r,n]=[n,r]);let o=!Re&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||o)&&!n&&(n=r,r=0),e.browserEvent&&e.browserEvent.altKey&&(n=n*this._options.fastScrollSensitivity,r=r*this._options.fastScrollSensitivity);let h=this._scrollable.getFutureScrollPosition(),l={};if(r){let a=cr*r,d=h.scrollTop-(a<0?Math.floor(a):Math.ceil(a));this._verticalScrollbar.writeScrollPosition(l,d)}if(n){let a=cr*n,d=h.scrollLeft-(a<0?Math.floor(a):Math.ceil(a));this._horizontalScrollbar.writeScrollPosition(l,d)}l=this._scrollable.validateScrollPosition(l),(h.scrollLeft!==l.scrollLeft||h.scrollTop!==l.scrollTop)&&(dr&&this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(l):this._scrollable.setScrollPositionNow(l),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" left":"",r=t?" top":"",n=i||t?" top-left-corner":"";this._leftShadowDomNode.setClassName(`shadow${s}`),this._topShadowDomNode.setClassName(`shadow${r}`),this._topLeftShadowDomNode.setClassName(`shadow${n}${r}${s}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),Qa)}},sh=class extends ih{constructor(e,t,i){super(e,t,i)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function rh(e){let t={lazyRender:typeof e.lazyRender<"u"?e.lazyRender:!1,className:typeof e.className<"u"?e.className:"",useShadows:typeof e.useShadows<"u"?e.useShadows:!0,handleMouseWheel:typeof e.handleMouseWheel<"u"?e.handleMouseWheel:!0,flipAxes:typeof e.flipAxes<"u"?e.flipAxes:!1,consumeMouseWheelIfScrollbarIsNeeded:typeof e.consumeMouseWheelIfScrollbarIsNeeded<"u"?e.consumeMouseWheelIfScrollbarIsNeeded:!1,alwaysConsumeMouseWheel:typeof e.alwaysConsumeMouseWheel<"u"?e.alwaysConsumeMouseWheel:!1,scrollYToX:typeof e.scrollYToX<"u"?e.scrollYToX:!1,mouseWheelScrollSensitivity:typeof e.mouseWheelScrollSensitivity<"u"?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof e.fastScrollSensitivity<"u"?e.fastScrollSensitivity:5,scrollPredominantAxis:typeof e.scrollPredominantAxis<"u"?e.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof e.mouseWheelSmoothScroll<"u"?e.mouseWheelSmoothScroll:!0,arrowSize:typeof e.arrowSize<"u"?e.arrowSize:11,listenOnDomNode:typeof e.listenOnDomNode<"u"?e.listenOnDomNode:null,horizontal:typeof e.horizontal<"u"?e.horizontal:1,horizontalScrollbarSize:typeof e.horizontalScrollbarSize<"u"?e.horizontalScrollbarSize:10,horizontalSliderSize:typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:0,horizontalHasArrows:typeof e.horizontalHasArrows<"u"?e.horizontalHasArrows:!1,vertical:typeof e.vertical<"u"?e.vertical:1,verticalScrollbarSize:typeof e.verticalScrollbarSize<"u"?e.verticalScrollbarSize:10,verticalHasArrows:typeof e.verticalHasArrows<"u"?e.verticalHasArrows:!1,verticalSliderSize:typeof e.verticalSliderSize<"u"?e.verticalSliderSize:0,scrollByPage:typeof e.scrollByPage<"u"?e.scrollByPage:!1};return t.horizontalSliderSize=typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof e.verticalSliderSize<"u"?e.verticalSliderSize:t.verticalScrollbarSize,Re&&(t.className+=" mac"),t}var b=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event?this._event:(this._event=(e,t,i)=>{if(this._disposed)return q(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let r=q(()=>{let n=this._listeners.indexOf(s);n!==-1&&this._listeners.splice(n,1)});return i&&(Array.isArray(i)?i.push(r):i.add(r)),r},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:i}=this._listeners[0];t.call(i,e);return}default:{let t=this._listeners.slice();for(let{fn:i,thisArgs:s}of t)i.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},_e;(e=>{function t(n,o){return n(h=>o.fire(h))}e.forward=t;function i(n,o){return(h,l,a)=>n(d=>h.call(l,o(d)),void 0,a)}e.map=i;function s(...n){return(o,h,l)=>{let a=new Cs;for(let d of n)a.add(d(c=>o.call(h,c)));return l&&(Array.isArray(l)?l.push(a):l.add(a)),a}}e.any=s;function r(n,o){return o(void 0),n(h=>o(h))}e.runAndSubscribe=r})(_e||(_e={}));var is=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._bufferService=i,this._optionsService=o,this._renderService=h,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;let l=this._register(new qa({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:a=>ni(s.window,a)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{l.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new sh(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},l)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","overviewRuler"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(r.onProtocolChange(a=>{this._scrollableElement.updateOptions({handleMouseWheel:!(a&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{e.style.backgroundColor=n.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=n.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(q(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register(q(()=>this._styleElement.remove())),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .scrollbar > .slider {",` background: ${n.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider:hover {",` background: ${n.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider.active {",` background: ${n.colors.scrollbarSliderActiveBackground.css};`,"}"].join(` +`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._scrollableElement.onScroll(a=>this._handleScroll(a))),this._register(En.addTarget(t)),this._register(B(t,Ce.Change,a=>this._handleGestureChange(a)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){!this._renderService||this._isSyncing||(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;i!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}_handleGestureChange(e){e.preventDefault(),e.stopPropagation();let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e.translationY})}};is=K([w(2,ce),w(3,Ne),w(4,Jr),w(5,lt),w(6,he),w(7,Fe)],is);var ss=class extends I{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(n=>this._removeDecoration(n))),this._register(q(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,i.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;let i=e.options.x??0;(e.options.anchor||"left")==="right"?t.style.right=i?`${i*this._renderService.dimensions.css.cell.width}px`:"":t.style.left=i?`${i*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};ss=K([w(1,ce),w(2,Ne),w(3,At),w(4,Fe)],ss);var nh=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(let t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position)){this._addLineToZone(t,e.marker.line);return}}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}},Be={full:0,left:0,center:0,right:0},qe={full:0,left:0,center:0,right:0},pt={full:0,left:0,center:0,right:0},oi=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=o,this._coreBrowserService=h,this._colorZoneStore=new nh,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(q(()=>this._canvas?.remove()));let l=this._canvas.getContext("2d");if(l)this._ctx=l;else throw new Error("Ctx cannot be null");this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onRender(()=>{(!this._containerHeight||this._containerHeight!==this._screenElement.clientHeight)&&(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("overviewRuler",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._queueRefresh(!0)}get _width(){return this._optionsService.options.overviewRuler?.width||0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);qe.full=this._canvas.width,qe.left=e,qe.center=t,qe.right=e,this._refreshDrawHeightConstants(),pt.full=1,pt.left=1,pt.center=1+qe.left,pt.right=1+qe.left+qe.center}_refreshDrawHeightConstants(){Be.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);Be.left=t,Be.center=t,Be.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let t of this._decorationService.decorations)this._colorZoneStore.addDecoration(t);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!=="full"&&this._renderColorZone(t);for(let t of e)t.position==="full"&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(pt[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-Be[e.position||"full"]/2),qe[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+Be[e.position||"full"]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};oi=K([w(2,ce),w(3,At),w(4,Fe),w(5,he),w(6,lt),w(7,Ne)],oi);var rs=class{constructor(e,t,i,s,r,n){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=n,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(e.keyCode===20||e.keyCode===229||e.keyCode===16||e.keyCode===17||e.keyCode===18)return!1;this._finalizeComposition(!1)}return e.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){let t={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let i;t.start+=this._dataAlreadySent.length,this._isComposing?i=this._textarea.value.substring(t.start,this._compositionPosition.start):i=this._textarea.value.substring(t.start),i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;let t=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(t,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;let e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){let t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};rs=K([w(2,ce),w(3,he),w(4,Xe),w(5,Fe)],rs);var ee=0,te=0,ie=0,$=0,ur={css:"#00000000",rgba:0},V;(e=>{function t(r,n,o,h){return h!==void 0?`#${Je(r)}${Je(n)}${Je(o)}${Je(h)}`:`#${Je(r)}${Je(n)}${Je(o)}`}e.toCss=t;function i(r,n,o,h=255){return(r<<24|n<<16|o<<8|h)>>>0}e.toRgba=i;function s(r,n,o,h){return{css:e.toCss(r,n,o,h),rgba:e.toRgba(r,n,o,h)}}e.toColor=s})(V||(V={}));var N;(e=>{function t(l,a){if($=(a.rgba&255)/255,$===1)return{css:a.css,rgba:a.rgba};let d=a.rgba>>24&255,c=a.rgba>>16&255,u=a.rgba>>8&255,f=l.rgba>>24&255,_=l.rgba>>16&255,p=l.rgba>>8&255;ee=f+Math.round((d-f)*$),te=_+Math.round((c-_)*$),ie=p+Math.round((u-p)*$);let y=V.toCss(ee,te,ie),E=V.toRgba(ee,te,ie);return{css:y,rgba:E}}e.blend=t;function i(l){return(l.rgba&255)===255}e.isOpaque=i;function s(l,a,d){let c=Jt.ensureContrastRatio(l.rgba,a.rgba,d);if(c)return V.toColor(c>>24&255,c>>16&255,c>>8&255)}e.ensureContrastRatio=s;function r(l){let a=(l.rgba|255)>>>0;return[ee,te,ie]=Jt.toChannels(a),{css:V.toCss(ee,te,ie),rgba:a}}e.opaque=r;function n(l,a){return $=Math.round(a*255),[ee,te,ie]=Jt.toChannels(l.rgba),{css:V.toCss(ee,te,ie,$),rgba:V.toRgba(ee,te,ie,$)}}e.opacity=n;function o(l,a){return $=l.rgba&255,n(l,$*a/255)}e.multiplyOpacity=o;function h(l){return[l.rgba>>24&255,l.rgba>>16&255,l.rgba>>8&255]}e.toColorRGB=h})(N||(N={}));var z;(e=>{let t,i;try{let r=document.createElement("canvas");r.width=1,r.height=1;let n=r.getContext("2d",{willReadFrequently:!0});n&&(t=n,t.globalCompositeOperation="copy",i=t.createLinearGradient(0,0,1,1))}catch{}function s(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),V.toColor(ee,te,ie);case 5:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),$=parseInt(r.slice(4,5).repeat(2),16),V.toColor(ee,te,ie,$);case 7:return{css:r,rgba:(parseInt(r.slice(1),16)<<8|255)>>>0};case 9:return{css:r,rgba:parseInt(r.slice(1),16)>>>0}}let n=r.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(n)return ee=parseInt(n[1]),te=parseInt(n[2]),ie=parseInt(n[3]),$=Math.round((n[5]===void 0?1:parseFloat(n[5]))*255),V.toColor(ee,te,ie,$);if(r==="transparent")return{css:"transparent",rgba:0};if(!t||!i)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=i,t.fillStyle=r,typeof t.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[ee,te,ie,$]=t.getImageData(0,0,1,1).data,$!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:V.toRgba(ee,te,ie,$),css:r}}e.toColor=s})(z||(z={}));var oe;(e=>{function t(s){return i(s>>16&255,s>>8&255,s&255)}e.relativeLuminance=t;function i(s,r,n){let o=s/255,h=r/255,l=n/255,a=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),d=h<=.03928?h/12.92:Math.pow((h+.055)/1.055,2.4),c=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4);return a*.2126+d*.7152+c*.0722}e.relativeLuminance2=i})(oe||(oe={}));var Jt;(e=>{function t(o,h){if($=(h&255)/255,$===1)return h;let l=h>>24&255,a=h>>16&255,d=h>>8&255,c=o>>24&255,u=o>>16&255,f=o>>8&255;return ee=c+Math.round((l-c)*$),te=u+Math.round((a-u)*$),ie=f+Math.round((d-f)*$),V.toRgba(ee,te,ie)}e.blend=t;function i(o,h,l){let a=oe.relativeLuminance(o>>8),d=oe.relativeLuminance(h>>8);if(Te(a,d)>8));if(_>8));return _>y?f:p}return f}let c=r(o,h,l),u=Te(a,oe.relativeLuminance(c>>8));if(u>8));return u>_?c:f}return c}}e.ensureContrastRatio=i;function s(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Te(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p0||f>0||_>0);)u-=Math.max(0,Math.ceil(u*.1)),f-=Math.max(0,Math.ceil(f*.1)),_-=Math.max(0,Math.ceil(_*.1)),p=Te(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));return(u<<24|f<<16|_<<8|255)>>>0}e.reduceLuminance=s;function r(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Te(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p>>0}e.increaseLuminance=r;function n(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}e.toChannels=n})(Jt||(Jt={}));function Je(e){let t=e.toString(16);return t.length<2?"0"+t:t}function Te(e,t){return e1){let d=this._getJoinedRanges(s,o,n,t,r);for(let c=0;c1){let a=this._getJoinedRanges(s,o,n,t,r);for(let d=0;d=Nt,k=m,v=this._workCell;if(u.length>0&&m===u[0][0]&&S){let T=u.shift(),gi=this._isCellInSelection(T[0],t);for(A=T[0]+1;A=T[1]),S?(C=!0,v=new oh(this._workCell,e.translateToString(!0,T[0],T[1]),T[1]-T[0]),k=T[1]-1,g=v.getWidth()):Nt=T[1]}let M=this._isCellInSelection(m,t),H=i&&m===n,ne=pi&&m>=a&&m<=d,Ee=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{Ee=!0});let Pe=v.getChars()||" ";if(Pe===" "&&(v.isUnderline()||v.isOverline())&&(Pe="\xA0"),xe=g*h-l.get(Pe,v.isBold(),v.isItalic()),!p)p=this._document.createElement("span");else if(y&&(M&&ye||!M&&!ye&&v.bg===R)&&(M&&ye&&f.selectionForeground||v.fg===x)&&v.extended.ext===L&&ne===P&&xe===re&&!H&&!C&&!Ee&&S){v.isInvisible()?E+=" ":E+=Pe,y++;continue}else y&&(p.textContent=E),p=this._document.createElement("span"),y=0,E="";if(R=v.bg,x=v.fg,L=v.extended.ext,P=ne,re=xe,ye=M,C&&n>=m&&n<=k&&(n=m),!this._coreService.isCursorHidden&&H&&this._coreService.isCursorInitialized){if(F.push("xterm-cursor"),this._coreBrowserService.isFocused)o&&F.push("xterm-cursor-blink"),F.push(s==="bar"?"xterm-cursor-bar":s==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":F.push("xterm-cursor-outline");break;case"block":F.push("xterm-cursor-block");break;case"bar":F.push("xterm-cursor-bar");break;case"underline":F.push("xterm-cursor-underline");break;default:break}}if(v.isBold()&&F.push("xterm-bold"),v.isItalic()&&F.push("xterm-italic"),v.isDim()&&F.push("xterm-dim"),v.isInvisible()?E=" ":E=v.getChars()||" ",v.isUnderline()&&(F.push(`xterm-underline-${v.extended.underlineStyle}`),E===" "&&(E="\xA0"),!v.isUnderlineColorDefault()))if(v.isUnderlineColorRGB())p.style.textDecorationColor=`rgb(${Tt.toColorRGB(v.getUnderlineColor()).join(",")})`;else{let T=v.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&v.isBold()&&T<8&&(T+=8),p.style.textDecorationColor=f.ansi[T].css}v.isOverline()&&(F.push("xterm-overline"),E===" "&&(E="\xA0")),v.isStrikethrough()&&F.push("xterm-strikethrough"),ne&&(p.style.textDecoration="underline");let J=v.getFgColor(),We=v.getFgColorMode(),le=v.getBgColor(),Ge=v.getBgColorMode(),ut=!!v.isInverse();if(ut){let T=J;J=le,le=T;let gi=We;We=Ge,Ge=gi}let ze,Ft,_t=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{T.options.layer!=="top"&&_t||(T.backgroundColorRGB&&(Ge=50331648,le=T.backgroundColorRGB.rgba>>8&16777215,ze=T.backgroundColorRGB),T.foregroundColorRGB&&(We=50331648,J=T.foregroundColorRGB.rgba>>8&16777215,Ft=T.foregroundColorRGB),_t=T.options.layer==="top")}),!_t&&M&&(ze=this._coreBrowserService.isFocused?f.selectionBackgroundOpaque:f.selectionInactiveBackgroundOpaque,le=ze.rgba>>8&16777215,Ge=50331648,_t=!0,f.selectionForeground&&(We=50331648,J=f.selectionForeground.rgba>>8&16777215,Ft=f.selectionForeground)),_t&&F.push("xterm-decoration-top");let Ke;switch(Ge){case 16777216:case 33554432:Ke=f.ansi[le],F.push(`xterm-bg-${le}`);break;case 50331648:Ke=V.toColor(le>>16,le>>8&255,le&255),this._addStyle(p,`background-color:#${(le>>>0).toString(16).padStart(6,"0")}`);break;default:ut?(Ke=f.foreground,F.push("xterm-bg-257")):Ke=f.background}switch(ze||v.isDim()&&(ze=N.multiplyOpacity(Ke,.5)),We){case 16777216:case 33554432:v.isBold()&&J<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(J+=8),this._applyMinimumContrast(p,Ke,f.ansi[J],v,ze,void 0)||F.push(`xterm-fg-${J}`);break;case 50331648:let T=V.toColor(J>>16&255,J>>8&255,J&255);this._applyMinimumContrast(p,Ke,T,v,ze,Ft)||this._addStyle(p,`color:#${J.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(p,Ke,f.foreground,v,ze,Ft)||ut&&F.push("xterm-fg-257")}F.length&&(p.className=F.join(" "),F.length=0),!H&&!C&&!Ee&&S?y++:p.textContent=E,xe!==this.defaultSpacing&&(p.style.letterSpacing=`${xe}px`),c.push(p),m=k}return p&&y&&(p.textContent=E),c}_applyMinimumContrast(e,t,i,s,r,n){if(this._optionsService.rawOptions.minimumContrastRatio===1||lh(s.getCode()))return!1;let o=this._getContrastCache(s),h;if(!r&&!n&&(h=o.getColor(t.rgba,i.rgba)),h===void 0){let l=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);h=N.ensureContrastRatio(r??t,n??i,l),o.setColor((r??t).rgba,(n??i).rgba,h??null)}return h?(this._addStyle(e,`color:${h.css}`),!0):!1}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){let i=this._selectionStart,s=this._selectionEnd;return!i||!s?!1:this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0]}};ns=K([w(1,en),w(2,he),w(3,Ne),w(4,Xe),w(5,At),w(6,lt)],ns);var dh=class{constructor(e=()=>new uh){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s=0;if(!t&&!i&&e.length===1&&(s=e.charCodeAt(0))<256){if(this._flat[s]!==-9999)return this._flat[s];let o=this._measure(e,0);return o>0&&(this._flat[s]=o),o}let r=e;t&&(r+="B"),i&&(r+="I");let n=this._holey.get(r);if(n===void 0){let o=0;t&&(o|=1),i&&(o|=2),n=this._measure(e,o),n>0&&this._holey.set(r,n)}return n}_measure(e,t){return this._canvasElements[t].measure(e)}},uh=class{constructor(){typeof OffscreenCanvas<"u"?(this._canvas=new OffscreenCanvas(1,1),this._ctx=_r(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=_r(this._canvas.getContext("2d")))}setFont(e,t,i,s){let r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}},_h=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1]){this.clear();return}let r=e.buffers.active.ydisp,n=t[1]-r,o=i[1]-r,h=Math.max(n,0),l=Math.min(o,e.rows-1);if(h>=e.rows||l<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=n,this.viewportEndRow=o,this.viewportCappedStartRow=h,this.viewportCappedEndRow=l,this.startCol=t[0],this.endCol=i[0]}isCellSelected(e,t,i){return this.hasSelection?(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol):!1}};function fh(){return new _h}var xi="xterm-dom-renderer-owner-",pe="xterm-rows",zt="xterm-fg-",fr="xterm-bg-",gt="xterm-focus",Kt="xterm-selection",Zt="xterm-cursor-blink-idle",ph=1,os=class extends I{constructor(e,t,i,s,r,n,o,h,l,a,d,c,u,f){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=n,this._linkifier2=o,this._charSizeService=l,this._optionsService=a,this._bufferService=d,this._coreService=c,this._coreBrowserService=u,this._themeService=f,this._terminalClass=ph++,this._rowElements=[],this._selectionRenderModel=fh(),this.onRequestRedraw=this._register(new b).event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(pe),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add(Kt),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=ch(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(_=>this._injectCss(_))),this._injectCss(this._themeService.colors),this._rowFactory=h.createInstance(ns,document),this._element.classList.add(xi+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(_=>this._handleLinkHover(_))),this._register(this._linkifier2.onHideLinkUnderline(_=>this._handleLinkLeave(_))),this._cursorBlinkStateManager=new gh(this._rowContainer,this._coreBrowserService),this._register(B(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register(q(()=>this._cursorBlinkStateManager.dispose())),this._register(q(()=>{this._element.classList.remove(xi+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new dh,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let i of this._rowElements)i.style.width=`${this.dimensions.css.canvas.width}px`,i.style.height=`${this.dimensions.css.cell.height}px`,i.style.lineHeight=`${this.dimensions.css.cell.height}px`,i.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .${pe} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${pe} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${pe} .xterm-dim { color: ${N.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;let i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${pe}.${gt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${pe}.${gt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .${pe}.${gt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${pe}.${Zt} .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${Kt} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Kt} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Kt} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[n,o]of e.ansi.entries())t+=`${this._terminalSelector} .${zt}${n} { color: ${o.css}; }${this._terminalSelector} .${zt}${n}.xterm-dim { color: ${N.multiplyOpacity(o,.5).css}; }${this._terminalSelector} .${fr}${n} { background-color: ${o.css}; }`;t+=`${this._terminalSelector} .${zt}257 { color: ${N.opaque(e.background).css}; }${this._terminalSelector} .${zt}257.xterm-dim { color: ${N.multiplyOpacity(N.opaque(e.background),.5).css}; }${this._terminalSelector} .${fr}257 { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let i=this._rowElements.length;i<=t;i++){let s=this._document.createElement("div");this._rowContainer.appendChild(s),this._rowElements.push(s)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(gt),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(gt),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,i){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i),this.renderRows(0,this._bufferService.rows-1),!e||!t||(this._selectionRenderModel.update(this._terminal,e,t,i),!this._selectionRenderModel.hasSelection))return;let s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,n=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,h=this._document.createDocumentFragment();if(i){let l=e[0]>t[0];h.appendChild(this._createSelectionElement(n,l?t[0]:e[0],l?e[0]:t[0],o-n+1))}else{let l=s===n?e[0]:0,a=n===r?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(n,l,a));let d=o-n-1;if(h.appendChild(this._createSelectionElement(n+1,0,this._bufferService.cols,d)),n!==o){let c=r===o?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(o,0,c))}}this._selectionContainer.appendChild(h)}_createSelectionElement(e,t,i,s=1){let r=this._document.createElement("div"),n=t*this.dimensions.css.cell.width,o=this.dimensions.css.cell.width*(i-t);return n+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-n),r.style.height=`${s*this.dimensions.css.cell.height}px`,r.style.top=`${e*this.dimensions.css.cell.height}px`,r.style.left=`${n}px`,r.style.width=`${o}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren()}renderRows(e,t){let i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),n=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,h=this._optionsService.rawOptions.cursorInactiveStyle;for(let l=e;l<=t;l++){let a=l+i.ydisp,d=this._rowElements[l],c=i.lines.get(a);if(!d||!c)break;d.replaceChildren(...this._rowFactory.createRow(c,a,a===s,o,h,r,n,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${xi}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,n){i<0&&(e=0),s<0&&(t=0);let o=this._bufferService.rows-1;i=Math.max(Math.min(i,o),0),s=Math.max(Math.min(s,o),0),r=Math.min(r,this._bufferService.cols);let h=this._bufferService.buffer,l=h.ybase+h.y,a=Math.min(h.x,r-1),d=this._optionsService.rawOptions.cursorBlink,c=this._optionsService.rawOptions.cursorStyle,u=this._optionsService.rawOptions.cursorInactiveStyle;for(let f=i;f<=s;++f){let _=f+h.ydisp,p=this._rowElements[f],y=h.lines.get(_);if(!p||!y)break;p.replaceChildren(...this._rowFactory.createRow(y,_,_===l,c,u,a,d,this.dimensions.css.cell.width,this._widthCache,n?f===i?e:0:-1,n?(f===s?t:r)-1:-1))}}};os=K([w(7,bs),w(8,ci),w(9,he),w(10,ce),w(11,Xe),w(12,Ne),w(13,lt)],os);var gh=class{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove(Zt),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove(Zt),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add(Zt),this._isIdlePaused=!0,this._idleTimeout=void 0}},as=class extends I{constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new b),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new mh(this._optionsService))}catch{this._measureStrategy=this._register(new vh(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};as=K([w(2,he)],as);var Rn=class extends I{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},vh=class extends Rn{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},mh=class extends Rn{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}},Sh=class extends I{constructor(e,t,i){super(),this._textarea=e,this._window=t,this.mainDocument=i,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new wh(this._window)),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new b),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange(s=>this._screenDprMonitor.setWindow(s))),this._register(_e.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(B(this._textarea,"focus",()=>this._isFocused=!0)),this._register(B(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},wh=class extends I{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new at),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(q(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=B(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}},bh=class extends I{constructor(){super(),this.linkProviders=[],this._register(q(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function Ps(e,t,i){let s=i.getBoundingClientRect(),r=e.getComputedStyle(i),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-s.left-n,t.clientY-s.top-o]}function yh(e,t,i,s,r,n,o,h,l){if(!n)return;let a=Ps(e,t,i);if(a)return a[0]=Math.ceil((a[0]+(l?o/2:0))/o),a[1]=Math.ceil(a[1]/h),a[0]=Math.min(Math.max(a[0],1),s+(l?1:0)),a[1]=Math.min(Math.max(a[1],1),r),a}var hs=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,i,s,r){return yh(window,e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){let i=Ps(be(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};hs=K([w(0,Fe),w(1,ci)],hs);var Ch=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh())),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,e):e,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,t):t,!this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}},Mn={};so(Mn,{getSafariVersion:()=>xh,isChromeOS:()=>An,isFirefox:()=>Ln,isLegacyEdge:()=>kh,isLinux:()=>As,isMac:()=>Rt,isNode:()=>ui,isSafari:()=>Pn,isWindows:()=>Tn});var ui=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),Ot=ui?"node":navigator.userAgent,Ts=ui?"node":navigator.platform,Ln=Ot.includes("Firefox"),kh=Ot.includes("Edge"),Pn=/^((?!chrome|android).)*safari/i.test(Ot);function xh(){if(!Pn)return 0;let e=Ot.match(/Version\/(\d+)/);return e===null||e.length<2?0:parseInt(e[1])}var Rt=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(Ts),Tn=["Windows","Win16","Win32","WinCE"].includes(Ts),As=Ts.indexOf("Linux")>=0,An=/\bCrOS\b/.test(Ot),On=class{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ir){s-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=r}this.clear()}},Eh=class extends On{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},Bh=class extends On{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},hi=!ui&&"requestIdleCallback"in window?Bh:Eh,Dh=class{constructor(){this._queue=new hi}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}},ls=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._rowCount=e,this._optionsService=i,this._charSizeService=s,this._coreService=r,this._coreBrowserService=h,this._renderer=this._register(new at),this._pausedResizeTask=new Dh,this._observerDisposable=this._register(new at),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new b),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new b),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new Ch((a,d)=>this._renderRows(a,d),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new Rh(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(q(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(o.onResize(()=>this._fullRefresh())),this._register(o.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(n.onDecorationRegistered(()=>this._fullRefresh())),this._register(n.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(o.cols,o.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(o.buffer.y,o.buffer.y,void 0,!0))),this._register(l.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(a=>this._registerIntersectionObserver(a,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){let i=new e.IntersectionObserver(s=>this._handleIntersectionChange(s[s.length-1]),{threshold:0});i.observe(t),this._observerDisposable.value=q(()=>i.disconnect())}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(t=>this.refreshRows(t.start,t.end,t.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};ls=K([w(2,he),w(3,ci),w(4,Xe),w(5,At),w(6,ce),w(7,Ne),w(8,lt)],ls);var Rh=class{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??(this._timeout=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3))}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Mh(e,t,i,s){let r=i.buffer.x,n=i.buffer.y;if(!i.buffer.hasScrollback)return Th(r,n,e,t,i,s)+_i(n,t,i,s)+Ah(r,n,e,t,i,s);let o;if(n===t)return o=r>e?"D":"C",Lt(Math.abs(r-e),Mt(o,s));o=n>t?"D":"C";let h=Math.abs(n-t),l=Ph(n>t?e:r,i)+(h-1)*i.cols+1+Lh(n>t?r:e,i);return Lt(l,Mt(o,s))}function Lh(e,t){return e-1}function Ph(e,t){return t.cols-e}function Th(e,t,i,s,r,n){return _i(t,s,r,n).length===0?"":Lt(Nn(e,t,e,t-st(t,r),!1,r).length,Mt("D",n))}function _i(e,t,i,s){let r=e-st(e,i),n=t-st(t,i),o=Math.abs(r-n)-Oh(e,t,i);return Lt(o,Mt(In(e,t),s))}function Ah(e,t,i,s,r,n){let o;_i(t,s,r,n).length>0?o=s-st(s,r):o=t;let h=s,l=Ih(e,t,i,s,r,n);return Lt(Nn(e,o,i,h,l==="C",r).length,Mt(l,n))}function Oh(e,t,i){let s=0,r=e-st(e,i),n=t-st(t,i);for(let o=0;o=0&&e0?o=s-st(s,r):o=t,e=i&&ot?"A":"B"}function Nn(e,t,i,s,r,n){let o=e,h=t,l="";for(;(o!==i||h!==s)&&h>=0&&hn.cols-1?(l+=n.buffer.translateBufferLineToString(h,!1,e,o),o=0,e=0,h++):!r&&o<0&&(l+=n.buffer.translateBufferLineToString(h,!1,0,e+1),o=n.cols-1,e=o,h--);return l+n.buffer.translateBufferLineToString(h,!1,e,o)}function Mt(e,t){return"\x1B"+(t?"O":"[")+e}function Lt(e,t){e=Math.floor(e);let i="";for(let s=0;sthis._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let e=this.selectionStart,t=this.selectionEnd;return!e||!t?!1:e[1]>t[1]||e[1]===t[1]&&e[0]>t[0]}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function pr(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}var Ei=50,Fh=15,Hh=50,Wh=500,zh="\xA0",Kh=new RegExp(zh,"g"),cs=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._element=e,this._screenElement=t,this._linkifier=i,this._bufferService=s,this._coreService=r,this._mouseService=n,this._optionsService=o,this._renderService=h,this._coreBrowserService=l,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new ke,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new b),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new b),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=a=>this._handleMouseMove(a),this._mouseUpListener=a=>this._handleMouseUp(a),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(a=>this._handleTrim(a)),this._register(this._bufferService.buffers.onBufferActivate(a=>this._handleBufferActivate(a))),this.enable(),this._model=new Nh(this._bufferService),this._activeSelectionMode=0,this._register(q(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(a=>{a.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";let i=this._bufferService.buffer,s=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return"";let r=e[0]r.replace(Kh," ")).join(Tn?`\r `:` -`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),Ps&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s||!t?!1:this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){let i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s?!1:this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=_r(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let s=this._getMouseBufferCoords(e);return s?(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=Ms(this._coreBrowserService.window,e,this._screenElement)[1],i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-ki),ki),t/=ki,t/Math.abs(t)+Math.round(t*(Oh-1)))}shouldForceSelection(e){return Dt?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),Ih)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){let t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);let i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&i.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(Dt&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&tthis._handleTrim(t))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){let r=e.loadCell(s,this._workCell).getChars().length;this._workCell.getWidth()===0?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;let r=this._bufferService.buffer,n=r.lines.get(e[1]);if(!n)return;let o=r.translateBufferLineToString(e[1],!1),h=this._convertViewportColToCharacterIndex(n,e[0]),l=h,a=e[0]-h,d=0,c=0,u=0,f=0;if(o.charAt(h)===" "){for(;h>0&&o.charAt(h-1)===" ";)h--;for(;l1&&(f+=A-1,l+=A-1);y>0&&h>0&&!this._isCharWordSeparator(n.loadCell(y-1,this._workCell));){n.loadCell(y-1,this._workCell);let R=this._workCell.getChars().length;this._workCell.getWidth()===0?(d++,y--):R>1&&(u+=R-1,h-=R-1),h--,y--}for(;E1&&(f+=R-1,l+=R-1),l++,E++}}l++;let _=h+a-d+u,p=Math.min(this._bufferService.cols,l-h+d+c-u-f);if(!(!t&&o.slice(h,l).trim()==="")){if(i&&_===0&&n.getCodePoint(0)!==32){let y=r.lines.get(e[1]-1);if(y&&n.isWrapped&&y.getCodePoint(this._bufferService.cols-1)!==32){let E=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(E){let A=this._bufferService.cols-E.start;_-=A,p+=A}}}if(s&&_+p===this._bufferService.cols&&n.getCodePoint(this._bufferService.cols-1)!==32){let y=r.lines.get(e[1]+1);if(y?.isWrapped&&y.getCodePoint(0)!==32){let E=this._getWordAt([0,e[1]+1],!1,!1,!0);E&&(p+=E.length)}}return{start:_,length:p}}}_selectWordAt(e,t){let i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return e.getWidth()===0?!1:this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=_r(i,this._bufferService.cols)}};hs=K([w(3,ce),w(4,je),w(5,ws),w(6,he),w(7,Ne),w(8,Ie)],hs);var fr=class{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}},pr=class{constructor(){this._color=new fr,this._css=new fr}setCss(e,t,i){this._css.set(e,t,i)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,i){this._color.set(e,t,i)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}},Y=Object.freeze((()=>{let e=[z.toColor("#2e3436"),z.toColor("#cc0000"),z.toColor("#4e9a06"),z.toColor("#c4a000"),z.toColor("#3465a4"),z.toColor("#75507b"),z.toColor("#06989a"),z.toColor("#d3d7cf"),z.toColor("#555753"),z.toColor("#ef2929"),z.toColor("#8ae234"),z.toColor("#fce94f"),z.toColor("#729fcf"),z.toColor("#ad7fa8"),z.toColor("#34e2e2"),z.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){let s=t[i/36%6|0],r=t[i/6%6|0],n=t[i%6];e.push({css:V.toCss(s,r,n),rgba:V.toRgba(s,r,n)})}for(let i=0;i<24;i++){let s=8+i*10;e.push({css:V.toCss(s,s,s),rgba:V.toRgba(s,s,s)})}return e})()),Je=z.toColor("#ffffff"),yt=z.toColor("#000000"),gr=z.toColor("#ffffff"),vr=yt,gt={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},Wh=Je,ls=class extends I{constructor(e){super(),this._optionsService=e,this._contrastCache=new pr,this._halfContrastCache=new pr,this._onChangeColors=this._register(new b),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:Je,background:yt,cursor:gr,cursorAccent:vr,selectionForeground:void 0,selectionBackgroundTransparent:gt,selectionBackgroundOpaque:N.blend(yt,gt),selectionInactiveBackgroundTransparent:gt,selectionInactiveBackgroundOpaque:N.blend(yt,gt),scrollbarSliderBackground:N.opacity(Je,.2),scrollbarSliderHoverBackground:N.opacity(Je,.4),scrollbarSliderActiveBackground:N.opacity(Je,.5),overviewRulerBorder:Je,ansi:Y.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=O(e.foreground,Je),t.background=O(e.background,yt),t.cursor=N.blend(t.background,O(e.cursor,gr)),t.cursorAccent=N.blend(t.background,O(e.cursorAccent,vr)),t.selectionBackgroundTransparent=O(e.selectionBackground,gt),t.selectionBackgroundOpaque=N.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=O(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=N.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?O(e.selectionForeground,cr):void 0,t.selectionForeground===cr&&(t.selectionForeground=void 0),N.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=N.opacity(t.selectionBackgroundTransparent,.3)),N.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=N.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=O(e.scrollbarSliderBackground,N.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=O(e.scrollbarSliderHoverBackground,N.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=O(e.scrollbarSliderActiveBackground,N.opacity(t.foreground,.5)),t.overviewRulerBorder=O(e.overviewRulerBorder,Wh),t.ansi=Y.slice(),t.ansi[0]=O(e.black,Y[0]),t.ansi[1]=O(e.red,Y[1]),t.ansi[2]=O(e.green,Y[2]),t.ansi[3]=O(e.yellow,Y[3]),t.ansi[4]=O(e.blue,Y[4]),t.ansi[5]=O(e.magenta,Y[5]),t.ansi[6]=O(e.cyan,Y[6]),t.ansi[7]=O(e.white,Y[7]),t.ansi[8]=O(e.brightBlack,Y[8]),t.ansi[9]=O(e.brightRed,Y[9]),t.ansi[10]=O(e.brightGreen,Y[10]),t.ansi[11]=O(e.brightYellow,Y[11]),t.ansi[12]=O(e.brightBlue,Y[12]),t.ansi[13]=O(e.brightMagenta,Y[13]),t.ansi[14]=O(e.brightCyan,Y[14]),t.ansi[15]=O(e.brightWhite,Y[15]),e.extendedAnsi){let i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;s"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};function Kh(e,t,i,s){let r={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:e.key==="UIKeyInputUpArrow"?t?r.key="\x1BOA":r.key="\x1B[A":e.key==="UIKeyInputLeftArrow"?t?r.key="\x1BOD":r.key="\x1B[D":e.key==="UIKeyInputRightArrow"?t?r.key="\x1BOC":r.key="\x1B[C":e.key==="UIKeyInputDownArrow"&&(t?r.key="\x1BOB":r.key="\x1B[B");break;case 8:r.key=e.ctrlKey?"\b":"\x7F",e.altKey&&(r.key="\x1B"+r.key);break;case 9:if(e.shiftKey){r.key="\x1B[Z";break}r.key=" ",r.cancel=!0;break;case 13:r.key=e.altKey?"\x1B\r":"\r",r.cancel=!0;break;case 27:r.key="\x1B",e.altKey&&(r.key="\x1B\x1B"),r.cancel=!0;break;case 37:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"D":t?r.key="\x1BOD":r.key="\x1B[D";break;case 39:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"C":t?r.key="\x1BOC":r.key="\x1B[C";break;case 38:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"A":t?r.key="\x1BOA":r.key="\x1B[A";break;case 40:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"B":t?r.key="\x1BOB":r.key="\x1B[B";break;case 45:!e.shiftKey&&!e.ctrlKey&&(r.key="\x1B[2~");break;case 46:n?r.key="\x1B[3;"+(n+1)+"~":r.key="\x1B[3~";break;case 36:n?r.key="\x1B[1;"+(n+1)+"H":t?r.key="\x1BOH":r.key="\x1B[H";break;case 35:n?r.key="\x1B[1;"+(n+1)+"F":t?r.key="\x1BOF":r.key="\x1B[F";break;case 33:e.shiftKey?r.type=2:e.ctrlKey?r.key="\x1B[5;"+(n+1)+"~":r.key="\x1B[5~";break;case 34:e.shiftKey?r.type=3:e.ctrlKey?r.key="\x1B[6;"+(n+1)+"~":r.key="\x1B[6~";break;case 112:n?r.key="\x1B[1;"+(n+1)+"P":r.key="\x1BOP";break;case 113:n?r.key="\x1B[1;"+(n+1)+"Q":r.key="\x1BOQ";break;case 114:n?r.key="\x1B[1;"+(n+1)+"R":r.key="\x1BOR";break;case 115:n?r.key="\x1B[1;"+(n+1)+"S":r.key="\x1BOS";break;case 116:n?r.key="\x1B[15;"+(n+1)+"~":r.key="\x1B[15~";break;case 117:n?r.key="\x1B[17;"+(n+1)+"~":r.key="\x1B[17~";break;case 118:n?r.key="\x1B[18;"+(n+1)+"~":r.key="\x1B[18~";break;case 119:n?r.key="\x1B[19;"+(n+1)+"~":r.key="\x1B[19~";break;case 120:n?r.key="\x1B[20;"+(n+1)+"~":r.key="\x1B[20~";break;case 121:n?r.key="\x1B[21;"+(n+1)+"~":r.key="\x1B[21~";break;case 122:n?r.key="\x1B[23;"+(n+1)+"~":r.key="\x1B[23~";break;case 123:n?r.key="\x1B[24;"+(n+1)+"~":r.key="\x1B[24~";break;default:if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&!e.metaKey)e.keyCode>=65&&e.keyCode<=90?r.key=String.fromCharCode(e.keyCode-64):e.keyCode===32?r.key="\0":e.keyCode>=51&&e.keyCode<=55?r.key=String.fromCharCode(e.keyCode-51+27):e.keyCode===56?r.key="\x7F":e.key==="/"?r.key="":e.keyCode===219?r.key="\x1B":e.keyCode===220?r.key="":e.keyCode===221&&(r.key="");else if((!i||s)&&e.altKey&&!e.metaKey){let o=zh[e.keyCode]?.[e.shiftKey?1:0];if(o)r.key="\x1B"+o;else if(e.keyCode>=65&&e.keyCode<=90){let h=e.ctrlKey?e.keyCode-64:e.keyCode+32,l=String.fromCharCode(h);e.shiftKey&&(l=l.toUpperCase()),r.key="\x1B"+l}else if(e.keyCode===32)r.key="\x1B"+(e.ctrlKey?"\0":" ");else if(e.key==="Dead"&&e.code.startsWith("Key")){let h=e.code.slice(3,4);e.shiftKey||(h=h.toLowerCase()),r.key="\x1B"+h,r.cancel=!0}}else if(i&&!e.altKey&&!e.ctrlKey&&!e.shiftKey&&e.metaKey)e.keyCode===65&&(r.type=1);else if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&e.key.length===1)r.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":r.key="";break;case"Digit2":r.key="\0";break;case"Digit6":r.key="";break}break}return r}var mr=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){let t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e){let t=this._getNumpadKeyCode(e);if(t!==void 0)return t;let i=this._getModifierKeyCode(e);if(i!==void 0)return i;let s=this._functionalKeyCodes[e.key];if(s!==void 0)return s;if(e.shiftKey&&e.code){if(e.code.startsWith("Digit")&&e.code.length===6){let r=e.code.charAt(5);if(r>="0"&&r<="9")return r.charCodeAt(0)}if(e.code.startsWith("Key")&&e.code.length===4)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(e.key.length===1){let r=e.key.codePointAt(0);return r>=65&&r<=90?r+32:r}}_isModifierKey(e){return e.key==="Shift"||e.key==="Control"||e.key==="Alt"||e.key==="Meta"}_buildCsiLetterSequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1B["+e}_buildSs3Sequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1BO"+e}_buildCsiTildeSequence(e,t,i,s){let r=s&&i!==1,n="\x1B["+e;return(t>0||r)&&(n+=";"+(t>0?t:"1"),r&&(n+=":"+i)),n+="~",n}_buildCsiUSequence(e,t,i,s,r,n,o){let h=!!(r&2),l=!!(r&4),a="\x1B["+t,d;l&&e.shiftKey&&e.key.length===1&&!n&&!o&&(d=e.key.codePointAt(0),a+=":"+d);let c=r&16&&s!==3&&e.key.length===1&&!n&&!o&&!e.ctrlKey?e.key.codePointAt(0):void 0,u=h&&s!==1&&(s===3||c===void 0);return(i>0||u||c!==void 0)&&(a+=";",i>0?a+=i:u&&(a+="1"),u&&(a+=":"+s)),c!==void 0&&(a+=";"+c),a+="u",a}evaluate(e,t,i=1){let s={type:0,cancel:!1,key:void 0},r=this._encodeModifiers(e),n=this._isModifierKey(e),o=!!(t&2);if(!o&&i===3||n&&!(t&8)&&!o)return s;let h=this._csiLetterKeys[e.key];if(h)return s.key=this._buildCsiLetterSequence(h,r,i,o),s.cancel=!0,s;let l=this._ss3FunctionKeys[e.key];if(l)return s.key=this._buildSs3Sequence(l,r,i,o),s.cancel=!0,s;let a=this._csiTildeKeys[e.key];if(a!==void 0)return s.key=this._buildCsiTildeSequence(a,r,i,o),s.cancel=!0,s;let d=this._getKeyCode(e);if(d===void 0)return s;let c=this._functionalKeyCodes[e.key]!==void 0||this._getNumpadKeyCode(e)!==void 0,u=!1;return t&8||o?u=!0:t&1&&(d===27||d===127||d===13||d===9||d===32||c?u=!0:r>0&&(e.shiftKey&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1?u=!1:u=!0)),u?(s.key=this._buildCsiUSequence(e,d,r,i,t,c,n),s.cancel=!0):e.key.length===1&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&(s.key=e.key),s}static shouldUseProtocol(e){return e>0}},$h=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){let t=this._codeToVk[e.code];return t!==void 0?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(e.key==="Enter")return 10;if(e.key==="Backspace")return 127}let t=this._keyToControlChar[e.key];if(t!==void 0)return t;if(e.key.length===1){let i=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(i>=65&&i<=90)return i-64;if(i>=97&&i<=122)return i-96}return i}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&(e.code==="ControlRight"?t|=4:t|=8),e.altKey&&(e.code==="AltRight"?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){let i=this._getVirtualKeyCode(e),s=this._getScanCode(e),r=this._getUnicodeChar(e),n=t?1:0,o=this._getControlKeyState(e);return{type:0,cancel:!0,key:`\x1B[${i};${s};${r};${n};${o};1_`}}},cs=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??(this._win32InputMode=new $h),this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??(this._kittyKeyboard=new mr),this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);let t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1):Kh(e,this._coreService.decPrivateModes.applicationCursorKeys,Dt,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);let t=this._coreService.kittyKeyboard.flags;if(this.useKitty&&t&2)return this._getKittyKeyboard().evaluate(e,t,3)}get useKitty(){let e=this._coreService.kittyKeyboard.flags;return!!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard&&mr.shouldUseProtocol(e))}get useWin32InputMode(){return!!(this._optionsService.rawOptions.vtExtensions?.win32InputMode&&this._coreService.decPrivateModes.win32InputMode)}};cs=K([w(0,je),w(1,he)],cs);var Uh=class{constructor(...e){this._entries=new Map;for(let[t,i]of e)this.set(t,i)}set(e,t){let i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(let[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}},qh=class{constructor(){this._services=new Uh,this._services.set(Ss,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){let i=ao(e).sort((n,o)=>n.index-o.index),s=[];for(let n of i){let o=this._services.get(n.id);if(!o)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${n.id._id}.`);s.push(o)}let r=i.length>0?i[0].index:t.length;if(t.length!==r)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${r+1} conflicts with ${t.length} static arguments`);return new e(...t,...s)}},Vh={trace:0,debug:1,info:2,warn:3,error:4,off:5},Yh="xterm.js: ",ds=class extends I{constructor(e){super(),this._optionsService=e,this._logLevel=5,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=Vh[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;s--)this._array[this._getCyclicIndex(s+i.length)]=this._array[this._getCyclicIndex(s)];for(let s=0;sthis._maxLength){let s=this._length+i.length-this._maxLength;this._startIndex+=s,this._length=this._maxLength,this.onTrimEmitter.fire(s)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let r=t-1;r>=0;r--)this.set(e+r+i,this.get(e+r));let s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>22,i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):s]}set(t,i){this._data[t*D+1]=i[0],i[1].length>1?(this._combined[t]=i[1],this._data[t*D+0]=t|2097152|i[2]<<22):this._data[t*D+0]=i[1].charCodeAt(0)|i[2]<<22}getWidth(t){return this._data[t*D+0]>>22}hasWidth(t){return this._data[t*D+0]&12582912}getFg(t){return this._data[t*D+1]}getBg(t){return this._data[t*D+2]}hasContent(t){return this._data[t*D+0]&4194303}getCodePoint(t){let i=this._data[t*D+0];return i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):i&2097151}isCombined(t){return this._data[t*D+0]&2097152}getString(t){let i=this._data[t*D+0];return i&2097152?this._combined[t]:i&2097151?Ve(i&2097151):""}isProtected(t){return this._data[t*D+2]&536870912}loadCell(t,i){return zt=t*D,i.content=this._data[zt+0],i.fg=this._data[zt+1],i.bg=this._data[zt+2],i.content&2097152&&(i.combinedData=this._combined[t]),i.bg&268435456&&(i.extended=this._extendedAttrs[t]),i}setCell(t,i){i.content&2097152&&(this._combined[t]=i.combinedData),i.bg&268435456&&(this._extendedAttrs[t]=i.extended),this._data[t*D+0]=i.content,this._data[t*D+1]=i.fg,this._data[t*D+2]=i.bg}setCellFromCodepoint(t,i,s,r){r.bg&268435456&&(this._extendedAttrs[t]=r.extended),this._data[t*D+0]=i|s<<22,this._data[t*D+1]=r.fg,this._data[t*D+2]=r.bg}addCodepointToCell(t,i,s){let r=this._data[t*D+0];r&2097152?this._combined[t]+=Ve(i):r&2097151?(this._combined[t]=Ve(r&2097151)+Ve(i),r&=-2097152,r|=2097152):r=i|1<<22,s&&(r&=-12582913,r|=s<<22),this._data[t*D+0]=r}insertCells(t,i,s){if(t%=this.length,t&&this.getWidth(t-1)===2&&this.setCellFromCodepoint(t-1,0,1,s),i=0;--r)this.setCell(t+i+r,this.loadCell(t+r,wr));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=s*4)this._data=new Uint32Array(this._data.buffer,0,s);else{let r=new Uint32Array(s);r.set(this._data),this._data=r}for(let r=this.length;r=t&&delete this._combined[h]}let n=Object.keys(this._extendedAttrs);for(let o=0;o=t&&delete this._extendedAttrs[h]}}return this.length=t,s*4*xi=0;--t)if(this._data[t*D+0]&4194303)return t+(this._data[t*D+0]>>22);return 0}getNoBgTrimmedLength(){for(let t=this.length-1;t>=0;--t)if(this._data[t*D+0]&4194303||this._data[t*D+2]&50331648)return t+(this._data[t*D+0]>>22);return 0}copyCellsFrom(t,i,s,r,n){let o=t._data;if(n)for(let l=r-1;l>=0;l--){for(let a=0;a=i&&(this._combined[a-i+s]=t._combined[a])}}translateToString(t,i,s,r){i=i??0,s=s??this.length,t&&(s=Math.min(s,this.getTrimmedLength())),r&&(r.length=0);let n="";for(;i>22||1}return r&&r.push(i),n}};function jh(e,t,i,s,r,n){let o=[];for(let h=0;h=h&&s0&&(y>c||d[y].getTrimmedLength()===0);y--)p++;p>0&&(o.push(h+d.length-p),o.push(p)),h+=d.length-1}return o}function Xh(e,t){let i=[],s=0,r=t[s],n=0;for(let o=0;oLt(e,a,t)).reduce((l,a)=>l+a),n=0,o=0,h=0;for(;hl&&(n-=l,o++);let a=e[o].getWidth(n-1)===2;a&&n--;let d=a?i-1:i;s.push(d),h+=d}return s}function Lt(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();let s=!e[t].hasContent(i-1)&&e[t].getWidth(i-1)===1,r=e[t+1].getWidth(0)===2;return s&&r?i-1:i}var Nn=class Fn{constructor(t){this.line=t,this.isDisposed=!1,this._disposables=[],this._id=Fn._nextId++,this._onDispose=this.register(new b),this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Ii(this._disposables),this._disposables.length=0)}register(t){return this._disposables.push(t),t}};Nn._nextId=1;var Zh=Nn,X={},Ze=X.B;X[0]={"`":"\u25C6",a:"\u2592",b:"\u2409",c:"\u240C",d:"\u240D",e:"\u240A",f:"\xB0",g:"\xB1",h:"\u2424",i:"\u240B",j:"\u2518",k:"\u2510",l:"\u250C",m:"\u2514",n:"\u253C",o:"\u23BA",p:"\u23BB",q:"\u2500",r:"\u23BC",s:"\u23BD",t:"\u251C",u:"\u2524",v:"\u2534",w:"\u252C",x:"\u2502",y:"\u2264",z:"\u2265","{":"\u03C0","|":"\u2260","}":"\xA3","~":"\xB7"};X.A={"#":"\xA3"};X.B=void 0;X[4]={"#":"\xA3","@":"\xBE","[":"ij","\\":"\xBD","]":"|","{":"\xA8","|":"f","}":"\xBC","~":"\xB4"};X.C=X[5]={"[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X.R={"#":"\xA3","@":"\xE0","[":"\xB0","\\":"\xE7","]":"\xA7","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xA8"};X.Q={"@":"\xE0","[":"\xE2","\\":"\xE7","]":"\xEA","^":"\xEE","`":"\xF4","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xFB"};X.K={"@":"\xA7","[":"\xC4","\\":"\xD6","]":"\xDC","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xDF"};X.Y={"#":"\xA3","@":"\xA7","[":"\xB0","\\":"\xE7","]":"\xE9","`":"\xF9","{":"\xE0","|":"\xF2","}":"\xE8","~":"\xEC"};X.E=X[6]={"@":"\xC4","[":"\xC6","\\":"\xD8","]":"\xC5","^":"\xDC","`":"\xE4","{":"\xE6","|":"\xF8","}":"\xE5","~":"\xFC"};X.Z={"#":"\xA3","@":"\xA7","[":"\xA1","\\":"\xD1","]":"\xBF","{":"\xB0","|":"\xF1","}":"\xE7"};X.H=X[7]={"@":"\xC9","[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X["="]={"#":"\xF9","@":"\xE0","[":"\xE9","\\":"\xE7","]":"\xEA","^":"\xEE",_:"\xE8","`":"\xF4","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xFB"};var br=4294967295,yr=class{constructor(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=Q.clone(),this.savedCharset=Ze,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=ke.fromCharData([0,"",1,0]),this._whitespaceCell=ke.fromCharData([0," ",1,32]),this._isClearing=!1,this._memoryCleanupQueue=new oi,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new Sr(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new Qt),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new Qt),this._whitespaceCell}getBlankLine(e,t){return new Ct(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&ebr?br:t}fillViewportRows(e){if(this.lines.length===0){e??(e=Q);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new Sr(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let i=this.getNullCell(Q),s=0,r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+n+1?(this.ybase--,n++,this.ydisp>0&&this.ydisp--):this.lines.push(new Ct(e,i)));else for(let o=this._rows;o>t;o--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(o),this.ybase=Math.max(this.ybase-o,0),this.ydisp=Math.max(this.ydisp-o,0),this.savedY=Math.max(this.savedY-o,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),n&&(this.y+=n),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let n=0;n0){let n=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,n)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend==="conpty"&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let i=this._optionsService.rawOptions.reflowCursorLine,s=jh(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(Q),i);if(s.length>0){let r=Xh(this.lines,s);Gh(this.lines,r.layout),this._reflowLargerAdjustViewport(e,t,r.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){let s=this.getNullCell(Q),r=i;for(;r-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;o--){let h=this.lines.get(o);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;let l=[h];for(;h.isWrapped&&o>0;)h=this.lines.get(--o),l.unshift(h);if(!i){let R=this.ybase+this.y;if(R>=o&&R0&&(r.push({start:o+l.length+n,newLines:f}),n+=f.length),l.push(...f);let _=d.length-1,p=d[_];p===0&&(_--,p=d[_]);let y=l.length-c-1,E=a;for(;y>=0;){let R=Math.min(E,p);if(l[_]===void 0)break;if(l[_].copyCellsFrom(l[y],E-R,p-R,R,!0),p-=R,p===0&&(_--,p=d[_]),E-=R,E===0){y--;let x=Math.max(y,0);E=Lt(l,x,this._cols)}}for(let R=0;R0;)this.ybase===0?this.y0){let o=[],h=[];for(let p=0;p=0;p--)if(c&&c.start>a+u){for(let y=c.newLines.length-1;y>=0;y--)this.lines.set(p--,c.newLines[y]);p++,o.push({index:a+1,amount:c.newLines.length}),u+=c.newLines.length,c=r[++d]}else this.lines.set(p,h[a--]);let f=0;for(let p=o.length-1;p>=0;p--)o[p].index+=f,this.lines.onInsertEmitter.fire(o[p]),f+=o[p].amount;let _=Math.max(0,l+n-this.lines.maxLength);_>0&&this.lines.onTrimEmitter.fire(_)}}translateBufferLineToString(e,t,i=0,s){let r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=i,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(i=>{t.line>=i.index&&(t.line+=i.amount)})),t.register(this.lines.onDelete(i=>{t.line>=i.index&&t.linei.index&&(t.line-=i.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}},Qh=class extends I{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new b),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new yr(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new yr(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}},Hn=2,Wn=1,us=class extends I{constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,Hn),this.rows=Math.max(e.rawOptions.rows||0,Wn),this.buffers=this._register(new Qh(e,this)),this._register(this.buffers.onBufferActivate(t=>{this._onScroll.fire(t.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let i=this.buffer,s;s=this._cachedBlankLine,(!s||s.length!==this.cols||s.getFg(0)!==e.fg||s.getBg(0)!==e.bg)&&(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;let r=i.ybase+i.scrollTop,n=i.ybase+i.scrollBottom;if(i.scrollTop===0){let o=i.lines.isFull;n===i.lines.length-1?o?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(n+1,0,s.clone()),o?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{let o=n-r+1;i.lines.shiftElements(r+1,o-1,-1),i.lines.set(n,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){let i=this.buffer;if(e<0){if(i.ydisp===0)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);let s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};us=K([w(0,he)],us);var st={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:Dt,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",overviewRuler:{},quirks:{},vtExtensions:{}},el=["normal","bold","100","200","300","400","500","600","700","800","900"],tl=class extends I{constructor(e){super(),this._onOptionChange=this._register(new b),this.onOptionChange=this._onOptionChange.event;let t={...st};for(let i in e)if(i in t)try{let s=e[i];t[i]=this._sanitizeAndValidateOption(i,s)}catch(s){console.error(s)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(q(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{e.indexOf(i)!==-1&&t()})}_setupOptions(){let e=i=>{if(!(i in st))throw new Error(`No option with key "${i}"`);return this.rawOptions[i]},t=(i,s)=>{if(!(i in st))throw new Error(`No option with key "${i}"`);s=this._sanitizeAndValidateOption(i,s),this.rawOptions[i]!==s&&(this.rawOptions[i]=s,this._onOptionChange.fire(i))};for(let i in this.rawOptions){let s={get:e.bind(this,i),set:t.bind(this,i)};Object.defineProperty(this.options,i,s)}}_sanitizeAndValidateOption(e,t){switch(e){case"cursorStyle":if(t||(t=st[e]),!il(t))throw new Error(`"${t}" is not a valid value for ${e}`);break;case"wordSeparator":t||(t=st[e]);break;case"fontWeight":case"fontWeightBold":if(typeof t=="number"&&1<=t&&t<=1e3)break;t=el.includes(t)?t:st[e];break;case"cursorWidth":t=Math.floor(t);case"lineHeight":case"tabStopWidth":if(t<1)throw new Error(`${e} cannot be less than 1, value: ${t}`);break;case"minimumContrastRatio":t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case"scrollback":if(t=Math.min(t,4294967295),t<0)throw new Error(`${e} cannot be less than 0, value: ${t}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(t<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case"rows":case"cols":if(!t&&t!==0)throw new Error(`${e} must be numeric, value: ${t}`);break;case"windowsPty":t=t??{};break}return t}};function il(e){return e==="block"||e==="underline"||e==="bar"}function kt(e,t=5){if(typeof e!="object")return e;let i=Array.isArray(e)?[]:{};for(let s in e)i[s]=t<=1?e[s]:e[s]&&kt(e[s],t-1);return i}var Cr=Object.freeze({insertMode:!1}),kr=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0}),xr=()=>({flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}),_s=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new b),this.onData=this._onData.event,this._onUserInput=this._register(new b),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new b),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=kt(Cr),this.decPrivateModes=kt(kr),this.kittyKeyboard=xr()}reset(){this.modes=kt(Cr),this.decPrivateModes=kt(kr),this.kittyKeyboard=xr()}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(s=>s.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(t=>t.charCodeAt(0))),this._onBinary.fire(e))}};_s=K([w(0,ce),w(1,Gr),w(2,he)],_s);var Er={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>e.button===4||e.action!==1?!1:(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>e.action!==32},DRAG:{events:23,restrict:e=>!(e.action===32&&e.button===3)},ANY:{events:31,restrict:e=>!0}};function Ei(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return e.button===4?(i|=64,i|=e.action):(i|=e.button&3,e.button&4&&(i|=64),e.button&8&&(i|=128),e.action===32?i|=32:e.action===0&&!t&&(i|=3)),i}var Bi=String.fromCharCode,Br={DEFAULT:e=>{let t=[Ei(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`\x1B[M${Bi(t[0])}${Bi(t[1])}${Bi(t[2])}`},SGR:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Ei(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Ei(e,!0)};${e.x};${e.y}${t}`}},fs=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._coreService=t,this._optionsService=i,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new b),this.onProtocolChange=this._onProtocolChange.event;for(let s of Object.keys(Er))this.addProtocol(s,Er[s]);for(let s of Object.keys(Br))this.addEncoding(s,Br[s]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,i){if(e.deltaY===0||e.shiftKey||t===void 0||i===void 0)return 0;let s=t/i,r=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(r/=s+0,Math.abs(e.deltaY)<50&&(r*=.3),this._wheelPartialScroll+=r,r=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows||e.button===4&&e.action===32||e.button===3&&e.action!==32||e.button!==4&&(e.action===2||e.action===3)||(e.col++,e.row++,e.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._activeEncoding==="SGR_PIXELS"))||!this._protocols[this._activeProtocol].restrict(e))return!1;let t=this._encodings[this._activeEncoding](e);return t&&(this._activeEncoding==="DEFAULT"?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(e&1),up:!!(e&2),drag:!!(e&4),move:!!(e&8),wheel:!!(e&16)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x||e.y!==t.y)return!1}else if(e.col!==t.col||e.row!==t.row)return!1;return!(e.button!==t.button||e.action!==t.action||e.ctrl!==t.ctrl||e.alt!==t.alt||e.shift!==t.shift)}};fs=K([w(0,ce),w(1,je),w(2,he)],fs);var Di=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],sl=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],j;function rl(e,t){let i=0,s=t.length-1,r;if(et[s][1])return!1;for(;s>=i;)if(r=i+s>>1,e>t[r][1])i=r+1;else if(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),s=i===0&&t!==0;if(s){let r=Qe.extractWidth(t);r===0?s=!1:r>i&&(i=r)}return Qe.createPropertyValue(0,i,s)}},Qe=class Jt{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new b,this.onChange=this._onChange.event;let t=new nl;this.register(t),this._active=t.version,this._activeProvider=t}static extractShouldJoin(t){return(t&1)!==0}static extractWidth(t){return t>>1&3}static extractCharKind(t){return t>>3}static createPropertyValue(t,i,s=!1){return(t&16777215)<<3|(i&3)<<1|(s?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(t){if(!this._providers[t])throw new Error(`unknown Unicode version "${t}"`);this._active=t,this._activeProvider=this._providers[t],this._onChange.fire(t)}register(t){this._providers[t.version]=t}wcwidth(t){return this._activeProvider.wcwidth(t)}getStringCellWidth(t){let i=0,s=0,r=t.length;for(let n=0;n=r)return i+this.wcwidth(o);let a=t.charCodeAt(n);56320<=a&&a<=57343?o=(o-55296)*1024+a-56320+65536:i+=this.wcwidth(a)}let h=this.charProperties(o,s),l=Jt.extractWidth(h);Jt.extractShouldJoin(h)&&(l-=Jt.extractWidth(s)),i+=l,s=h}return i}charProperties(t,i){return this._activeProvider.charProperties(t,i)}},ol=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}};function Dr(e){let t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1)?.get(e.cols-1),i=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);i&&t&&(i.isWrapped=t[3]!==0&&t[3]!==32)}var vt=2147483647,al=256,zn=class ps{constructor(t=32,i=32){if(this.maxLength=t,this.maxSubParamsLength=i,i>al)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(t),this.length=0,this._subParams=new Int32Array(i),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(t),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let i=new ps;if(!t.length)return i;for(let s=Array.isArray(t[0])?1:0;s>8,r=this._subParamsIdx[i]&255;r-s>0&&t.push(Array.prototype.slice.call(this._subParams,s,r))}return t}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(t){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=t>vt?vt:t}addSubParam(t){if(this._digitIsSub=!0,!!this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=t>vt?vt:t,this._subParamsIdx[this.length-1]++}}hasSubParams(t){return(this._subParamsIdx[t]&255)-(this._subParamsIdx[t]>>8)>0}getSubParams(t){let i=this._subParamsIdx[t]>>8,s=this._subParamsIdx[t]&255;return s-i>0?this._subParams.subarray(i,s):null}getSubParamsAll(){let t={};for(let i=0;i>8,r=this._subParamsIdx[i]&255;r-s>0&&(t[i]=this._subParams.slice(s,r))}return t}addDigit(t){let i;if(this._rejectDigits||!(i=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let s=this._digitIsSub?this._subParams:this.params,r=s[i-1];s[i-1]=~r?Math.min(r*10+t,vt):t}},mt=[],hl=class{constructor(){this._state=0,this._active=mt,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=mt}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=mt,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||mt,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",at(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){if(this._state!==3){if(this._state===1)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=mt,this._id=-1,this._state=0}}},Kn=class $n{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=at(t,i,s),this._data.length>$n._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};Kn._payloadLimit=1e7;var ge=Kn,St=[],ll=class{constructor(){this._handlers=Object.create(null),this._active=St,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=St}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=St,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||St,!this._active.length)this._handlerFb(this._ident,"HOOK",t);else for(let i=this._active.length-1;i>=0;i--)this._active[i].hook(t)}put(e,t,i){if(!this._active.length)this._handlerFb(this._ident,"PUT",at(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}unhook(e,t=!0){if(!this._active.length)this._handlerFb(this._ident,"UNHOOK",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].unhook(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=St,this._ident=0}},xt=new zn;xt.addParam(0);var Un=class qn{constructor(t){this._handler=t,this._data="",this._params=xt,this._hitLimit=!1}hook(t){this._params=t.length>1||t.params[0]?t.clone():xt,this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=at(t,i,s),this._data.length>qn._payloadLimit&&(this._data="",this._hitLimit=!0))}unhook(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data,this._params),i instanceof Promise))return i.then(s=>(this._params=xt,this._data="",this._hitLimit=!1,s));return this._params=xt,this._data="",this._hitLimit=!1,i}};Un._payloadLimit=1e7;var Rr=Un,rt=[],cl=class{constructor(){this._state=0,this._active=rt,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=rt}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=rt,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||rt,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",at(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){this._state!==3&&(this._state===1&&t0&&this._put(e,t,i))}end(e,t=!0){if(this._state!==0){if(this._state!==3){if(this._state===1){this._active=rt,this._id=-1,this._state=0;return}if(!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}}this._active=rt,this._id=-1,this._state=0}}},Vn=class Yn{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=at(t,i,s),this._data.length>Yn._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};Vn._payloadLimit=1e7;var dl=Vn,ul=class{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rh),i=(o,h)=>t.slice(o,h),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));let n=i(0,15);e.setDefault(1,0),e.addMany(s,0,2,0);for(let o of n)e.addMany([24,26,153,154],o,3,0),e.addMany(i(128,144),o,3,0),e.addMany(i(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158],o,0,7),e.add(159,o,15,14),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,15,14),e.addMany(s,14,16,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany([27,156,24,26],14,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(28,32),9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(i(28,32),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(28,32),10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(28,32),12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(ue,0,2,0),e.add(ue,8,5,8),e.add(ue,6,0,6),e.add(ue,11,0,11),e.add(ue,13,13,13),e.add(ue,14,16,14),e})(),fl=class extends I{constructor(e=_l){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new zn,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(t,i,s)=>{},this._executeHandlerFb=t=>{},this._csiHandlerFb=(t,i)=>{},this._escHandlerFb=t=>{},this._errorHandlerFb=t=>t,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(q(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new hl),this._dcsParser=this._register(new ll),this._apcParser=this._register(new cl),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let r=0;rn||n>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=n}}if(e.final.length!==1)throw new Error("final must be a single byte");let s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){var r;let i=this._identifier(e,[48,126]);(r=this._escHandlers)[i]??(r[i]=[]);let s=this._escHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){var r;let i=this._identifier(e);(r=this._csiHandlers)[i]??(r[i]=[]);let s=this._csiHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return this._apcParser.registerHandler(e,t)}clearApcHandler(e){this._apcParser.clearHandler(e)}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s=0,r=0,n=0,o;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(i===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");let h=this._parseStack.handlers,l=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](this._params),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 4:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&255}for(let h=n;h>8){case 2:for(let u=h+1;;++u){if(u>=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=0&&(o=l[a](this._params),o!==!0);a--)if(o instanceof Promise)return this._preserveStack(3,l,a,r,h),o;a<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}while(++h47&&s<60);h--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:let d=this._escHandlers[this._collect<<8|s],c=d?d.length-1:-1;for(;c>=0&&(o=d[c](),o!==!0);c--)if(o instanceof Promise)return this._preserveStack(4,d,c,r,h),o;c<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let u=h+1;;++u)if(u>=t||(s=e[u])===24||s===26||s===27||s>127&&s=t||(s=e[u])<32||s>127&&s=t||(s=e[u])===24||s===26||s===27||s===156||s>127&&s>4:n>>8}return s}}function Ri(e,t){let i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}function vl(e,t=16){let[i,s,r]=e;return`rgb:${Ri(i,t)}/${Ri(s,t)}/${Ri(r,t)}`}var ml="6.1.0-beta.141",Sl={"(":0,")":1,"*":2,"+":3,"-":1,".":2},qe=131072,Lr=10;function Pr(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var Tr=5e3,Ar=0,wl=class extends I{constructor(e,t,i,s,r,n,o,h,l=new fl){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=n,this._coreMouseService=o,this._unicodeService=h,this._parser=l,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new no,this._utf8Decoder=new oo,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone(),this._onRequestBell=this._register(new b),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new b),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new b),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new b),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new b),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new b),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new b),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new b),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new b),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new b),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new gs(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(a=>this._activeBuffer=a.activeBuffer)),this._parser.setCsiHandlerFallback((a,d)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(a),params:d.toArray()})}),this._parser.setEscHandlerFallback(a=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(a)})}),this._parser.setExecuteHandlerFallback(a=>{this._logService.debug("Unknown EXECUTE code: ",{code:a})}),this._parser.setOscHandlerFallback((a,d,c)=>{this._logService.debug("Unknown OSC code: ",{identifier:a,action:d,data:c})}),this._parser.setDcsHandlerFallback((a,d,c)=>{d==="HOOK"&&(c=c.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(a),action:d,payload:c})}),this._parser.setPrintHandler((a,d,c)=>this.print(a,d,c)),this._parser.registerCsiHandler({final:"@"},a=>this.insertChars(a)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},a=>this.scrollLeft(a)),this._parser.registerCsiHandler({final:"A"},a=>this.cursorUp(a)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},a=>this.scrollRight(a)),this._parser.registerCsiHandler({final:"B"},a=>this.cursorDown(a)),this._parser.registerCsiHandler({final:"C"},a=>this.cursorForward(a)),this._parser.registerCsiHandler({final:"D"},a=>this.cursorBackward(a)),this._parser.registerCsiHandler({final:"E"},a=>this.cursorNextLine(a)),this._parser.registerCsiHandler({final:"F"},a=>this.cursorPrecedingLine(a)),this._parser.registerCsiHandler({final:"G"},a=>this.cursorCharAbsolute(a)),this._parser.registerCsiHandler({final:"H"},a=>this.cursorPosition(a)),this._parser.registerCsiHandler({final:"I"},a=>this.cursorForwardTab(a)),this._parser.registerCsiHandler({final:"J"},a=>this.eraseInDisplay(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},a=>this.eraseInDisplay(a,!0)),this._parser.registerCsiHandler({final:"K"},a=>this.eraseInLine(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},a=>this.eraseInLine(a,!0)),this._parser.registerCsiHandler({final:"L"},a=>this.insertLines(a)),this._parser.registerCsiHandler({final:"M"},a=>this.deleteLines(a)),this._parser.registerCsiHandler({final:"P"},a=>this.deleteChars(a)),this._parser.registerCsiHandler({final:"S"},a=>this.scrollUp(a)),this._parser.registerCsiHandler({final:"T"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"X"},a=>this.eraseChars(a)),this._parser.registerCsiHandler({final:"Z"},a=>this.cursorBackwardTab(a)),this._parser.registerCsiHandler({final:"^"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"`"},a=>this.charPosAbsolute(a)),this._parser.registerCsiHandler({final:"a"},a=>this.hPositionRelative(a)),this._parser.registerCsiHandler({final:"b"},a=>this.repeatPrecedingCharacter(a)),this._parser.registerCsiHandler({final:"c"},a=>this.sendDeviceAttributesPrimary(a)),this._parser.registerCsiHandler({prefix:">",final:"c"},a=>this.sendDeviceAttributesSecondary(a)),this._parser.registerCsiHandler({final:"d"},a=>this.linePosAbsolute(a)),this._parser.registerCsiHandler({final:"e"},a=>this.vPositionRelative(a)),this._parser.registerCsiHandler({final:"f"},a=>this.hVPosition(a)),this._parser.registerCsiHandler({final:"g"},a=>this.tabClear(a)),this._parser.registerCsiHandler({final:"h"},a=>this.setMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"h"},a=>this.setModePrivate(a)),this._parser.registerCsiHandler({final:"l"},a=>this.resetMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"l"},a=>this.resetModePrivate(a)),this._parser.registerCsiHandler({final:"m"},a=>this.charAttributes(a)),this._parser.registerCsiHandler({final:"n"},a=>this.deviceStatus(a)),this._parser.registerCsiHandler({prefix:"?",final:"n"},a=>this.deviceStatusPrivate(a)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},a=>this.softReset(a)),this._parser.registerCsiHandler({prefix:">",final:"q"},a=>this.sendXtVersion(a)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},a=>this.setCursorStyle(a)),this._parser.registerCsiHandler({final:"r"},a=>this.setScrollRegion(a)),this._parser.registerCsiHandler({final:"s"},a=>this.saveCursor(a)),this._parser.registerCsiHandler({final:"t"},a=>this.windowOptions(a)),this._parser.registerCsiHandler({final:"u"},a=>this.restoreCursor(a)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},a=>this.insertColumns(a)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},a=>this.deleteColumns(a)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},a=>this.selectProtected(a)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},a=>this.requestMode(a,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},a=>this.requestMode(a,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},a=>this.kittyKeyboardSet(a)),this._parser.registerCsiHandler({prefix:"?",final:"u"},a=>this.kittyKeyboardQuery(a)),this._parser.registerCsiHandler({prefix:">",final:"u"},a=>this.kittyKeyboardPush(a)),this._parser.registerCsiHandler({prefix:"<",final:"u"},a=>this.kittyKeyboardPop(a)),this._parser.setExecuteHandler("\x07",()=>this.bell()),this._parser.setExecuteHandler(` -`,()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler(" ",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("\x84",()=>this.index()),this._parser.setExecuteHandler("\x85",()=>this.nextLine()),this._parser.setExecuteHandler("\x88",()=>this.tabSet()),this._parser.registerOscHandler(0,new ge(a=>(this.setTitle(a),this.setIconName(a),!0))),this._parser.registerOscHandler(1,new ge(a=>this.setIconName(a))),this._parser.registerOscHandler(2,new ge(a=>this.setTitle(a))),this._parser.registerOscHandler(4,new ge(a=>this.setOrReportIndexedColor(a))),this._parser.registerOscHandler(8,new ge(a=>this.setHyperlink(a))),this._parser.registerOscHandler(10,new ge(a=>this.setOrReportFgColor(a))),this._parser.registerOscHandler(11,new ge(a=>this.setOrReportBgColor(a))),this._parser.registerOscHandler(12,new ge(a=>this.setOrReportCursorColor(a))),this._parser.registerOscHandler(104,new ge(a=>this.restoreIndexedColor(a))),this._parser.registerOscHandler(110,new ge(a=>this.restoreFgColor(a))),this._parser.registerOscHandler(111,new ge(a=>this.restoreBgColor(a))),this._parser.registerOscHandler(112,new ge(a=>this.restoreCursorColor(a))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(let a in X)this._parser.registerEscHandler({intermediates:"(",final:a},()=>this.selectCharset("("+a)),this._parser.registerEscHandler({intermediates:")",final:a},()=>this.selectCharset(")"+a)),this._parser.registerEscHandler({intermediates:"*",final:a},()=>this.selectCharset("*"+a)),this._parser.registerEscHandler({intermediates:"+",final:a},()=>this.selectCharset("+"+a)),this._parser.registerEscHandler({intermediates:"-",final:a},()=>this.selectCharset("-"+a)),this._parser.registerEscHandler({intermediates:".",final:a},()=>this.selectCharset("."+a)),this._parser.registerEscHandler({intermediates:"/",final:a},()=>this.selectCharset("/"+a));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(a=>(this._logService.error("Parsing error: ",a),a)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new Rr((a,d)=>this.requestStatusString(a,d)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=3){let t,i=new Promise((s,r)=>{t=setTimeout(()=>r("#SLOW_TIMEOUT"),Tr)});Promise.race([e,i]).then(()=>{t!==void 0&&clearTimeout(t)},s=>{if(t!==void 0&&clearTimeout(t),s!=="#SLOW_TIMEOUT")throw s;console.warn(`async parser handler taking longer than ${Tr} ms`)})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,n=0,o=this._parseStack.paused;if(o){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>qe&&(n=this._parseStack.position+qe)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e=="string"?` "${e}"`:` "${Array.prototype.map.call(e,a=>String.fromCharCode(a)).join("")}"`}`),this._logService.logLevel===0&&this._logService.trace("parsing data (codes)",typeof e=="string"?e.split("").map(a=>a.charCodeAt(0)):e),this._parseBuffer.lengthqe)for(let a=n;a0&&c.getWidth(this._activeBuffer.x-1)===2&&c.setCellFromCodepoint(this._activeBuffer.x-1,0,1,d);let u=this._parser.precedingJoinState;for(let f=t;fh){if(l){let E=c,A=this._activeBuffer.x-y;if(this._activeBuffer.x=y,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),c=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!c)return;for(y>0&&c instanceof Ct&&c.copyCellsFrom(E,A,0,y,!1);A=0;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d);continue}if(a&&(c.insertCells(this._activeBuffer.x,r-y,this._activeBuffer.getNullCell(d)),c.getWidth(h-1)===2&&c.setCellFromCodepoint(h-1,0,1,d)),c.setCellFromCodepoint(this._activeBuffer.x++,s,r,d),r>0)for(;--r;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d)}this._parser.precedingJoinState=u,this._activeBuffer.x0&&c.getWidth(this._activeBuffer.x)===0&&!c.hasContent(this._activeBuffer.x)&&c.setCellFromCodepoint(this._activeBuffer.x,0,1,d),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final==="t"&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,i=>Pr(i.params[0],this._optionsService.rawOptions.windowOptions)?t(i):!0):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new Rr(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new ge(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new dl(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){let n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n&&(n.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(n.isWrapped=!1))}_resetBufferLine(e,t=!1){let i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let i;switch(e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);i=this._bufferService.cols){let r=this._activeBuffer.lines.get(i+1);r&&(r.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+i)?.getTrimmedLength(););for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let s=this._activeBuffer.lines.length-this._bufferService.rows;s>0&&(this._activeBuffer.lines.trimStart(s),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-s,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-s,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let l=h;for(let a=1;a0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("\x1B[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("\x1B[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("\x1B[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("\x1B[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("\x1B[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`\x1BP>|xterm.js(${ml})\x1B\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(p[p.NOT_RECOGNIZED=0]="NOT_RECOGNIZED",p[p.SET=1]="SET",p[p.RESET=2]="RESET",p[p.PERMANENTLY_SET=3]="PERMANENTLY_SET",p[p.PERMANENTLY_RESET=4]="PERMANENTLY_RESET"))(void 0||(i={}));let s=this._coreService.decPrivateModes,{activeProtocol:r,activeEncoding:n}=this._coreMouseService,o=this._coreService,{buffers:h,cols:l}=this._bufferService,{active:a,alt:d}=h,c=this._optionsService.rawOptions,u=(p,y)=>(o.triggerDataEvent(`\x1B[${t?"":"?"}${p};${y}$y`),!0),f=p=>p?1:2,_=e.params[0];return t?_===2?u(_,4):_===4?u(_,f(o.modes.insertMode)):_===12?u(_,3):_===20?u(_,f(c.convertEol)):u(_,0):_===1?u(_,f(s.applicationCursorKeys)):_===3?u(_,c.windowOptions.setWinLines?l===80?2:l===132?1:0:0):_===6?u(_,f(s.origin)):_===7?u(_,f(s.wraparound)):_===8?u(_,3):_===9?u(_,f(r==="X10")):_===12?u(_,f(c.cursorBlink)):_===25?u(_,f(!o.isCursorHidden)):_===45?u(_,f(s.reverseWraparound)):_===66?u(_,f(s.applicationKeypad)):_===67?u(_,4):_===1e3?u(_,f(r==="VT200")):_===1002?u(_,f(r==="DRAG")):_===1003?u(_,f(r==="ANY")):_===1004?u(_,f(s.sendFocus)):_===1005?u(_,4):_===1006?u(_,f(n==="SGR")):_===1015?u(_,4):_===1016?u(_,f(n==="SGR_PIXELS")):_===1048?u(_,1):_===47||_===1047||_===1049?u(_,f(a===d)):_===2004?u(_,f(s.bracketedPasteMode)):_===2026?u(_,f(s.synchronizedOutput)):_===9001&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?u(_,f(s.win32InputMode)):u(_,0)}_updateAttrColor(e,t,i,s,r){return t===2?(e|=50331648,e&=-16777216,e|=Pt.fromColorRGB([i,s,r])):t===5&&(e&=-50331904,e|=33554432|i&255),e}_extractColor(e,t,i){let s=[0,0,-1,0,0,0],r=0,n=0;do{if(s[n+r]=e.params[t+n],e.hasSubParams(t+n)){let o=e.getSubParams(t+n),h=0;do s[1]===5&&(r=1),s[n+h+1+r]=o[h];while(++h=2||s[1]===2&&n+r>=5)break;s[1]&&(r=1)}while(++n+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=Q.fg,e.bg=Q.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,i,s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-50331904,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-50331904,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-50331904,s.fg|=16777216|i-90|8):i>=100&&i<=107?(s.bg&=-50331904,s.bg|=16777216|i-100|8):i===0?this._processSGR0(s):i===1?s.fg|=134217728:i===3?s.bg|=67108864:i===4?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):i===5?s.fg|=536870912:i===7?s.fg|=67108864:i===8?s.fg|=1073741824:i===9?s.fg|=2147483648:i===2?s.bg|=134217728:i===21?this._processUnderline(2,s):i===22?(s.fg&=-134217729,s.bg&=-134217729):i===23?s.bg&=-67108865:i===24?(s.fg&=-268435457,this._processUnderline(0,s)):i===25?s.fg&=-536870913:i===27?s.fg&=-67108865:i===28?s.fg&=-1073741825:i===29?s.fg&=2147483647:i===39?(s.fg&=-67108864,s.fg|=Q.fg&16777215):i===49?(s.bg&=-67108864,s.bg|=Q.bg&16777215):i===38||i===48||i===58?r+=this._extractColor(e,r,s):i===53?s.bg|=1073741824:i===55?s.bg&=-1073741825:i===221&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.fg&=-134217729:i===222&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.bg&=-134217729:i===59?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("\x1B[0n");break;case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[${t};${i}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[?${t};${i}R`);break;case 15:break;case 25:break;case 26:break;case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??!0)&&this._onRequestColorSchemeQuery.fire();break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=Q.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar";break}let i=t%2===1;this._coreService.decPrivateModes.cursorBlink=i}return!0}setScrollRegion(e){let t=e.params[0]||1,i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||i===0)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!Pr(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`\x1B[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>Lr&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>Lr&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let t=0;t1;){let s=i.shift(),r=i.shift();if(/^\d+$/.exec(s)){let n=parseInt(s);if(Or(n))if(r==="?")t.push({type:0,index:n});else{let o=Mr(r);o&&t.push({type:1,index:n,color:o})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(";");if(t===-1)return!0;let i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):i.trim()?!1:this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let i=e.split(":"),s,r=i.findIndex(n=>n.startsWith("id="));return r!==-1&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let i=e.split(";");for(let s=0;s=this._specialColors.length);++s,++t)if(i[s]==="?")this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let r=Mr(i[s]);r&&this._onColor.fire([{type:1,index:this._specialColors[t],color:r}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],i=e.split(";");for(let s=0;s=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new ke;e.content=1<<22|69,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`\x1B${o}\x1B\\`),!0),s=this._bufferService.buffer,r=this._optionsService.rawOptions;return i(e==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:e==='"p'?'P1$r61;1"p':e==="r"?`P1$r${s.scrollTop+1};${s.scrollBottom+1}r`:e==="m"?"P1$r0m":e===" q"?`P1$r${{block:2,underline:4,bar:6}[r.cursorStyle]-(r.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t;break}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`\x1B[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let r=0;r0;r++)i.flags=s.pop();return s.length===0&&t>0&&(i.flags=0),!0}},gs=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(Ar=e,e=t,t=Ar),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};gs=K([w(0,ce)],gs);function Or(e){return 0<=e&&e<256}var bl=5e7,Ir=12,yl=50,Cl=class extends I{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._isSyncWriting)return;this._isSyncWriting=!0;let e;for(;e=this._writeBuffer.shift();){this._action(e);let t=this._callbacks.shift();t&&t()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1}writeSync(e,t){if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let i;for(;i=this._writeBuffer.shift();){this._action(i);let s=this._callbacks.shift();s&&s()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>bl)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){let i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let s=this._writeBuffer[this._bufferOffset],r=this._action(s,t);if(r){let o=h=>performance.now()-i>=Ir?setTimeout(()=>this._innerWrite(0,h)):this._innerWrite(i,h);r.catch(h=>(queueMicrotask(()=>{throw h}),Promise.resolve(!1))).then(o);return}let n=this._callbacks[this._bufferOffset];if(n&&n(),this._bufferOffset++,this._pendingData-=s.length,performance.now()-i>=Ir)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>yl&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}},vs=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){let t=this._bufferService.buffer;if(e.id===void 0){let h=t.addMarker(t.ybase+t.y),l={data:e,id:this._nextId++,lines:[h]};return h.onDispose(()=>this._removeMarkerFromLink(l,h)),this._dataByLinkId.set(l.id,l),l.id}let i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;let n=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[n]};return n.onDispose(()=>this._removeMarkerFromLink(o,n)),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){let i=this._dataByLinkId.get(e);if(i&&i.lines.every(s=>s.line!==t)){let s=this._bufferService.buffer.addMarker(t);i.lines.push(s),s.onDispose(()=>this._removeMarkerFromLink(i,s))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){let i=e.lines.indexOf(t);i!==-1&&(e.lines.splice(i,1),e.lines.length===0&&(e.data.id!==void 0&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};vs=K([w(0,ce)],vs);var Nr=!1,kl=class extends I{constructor(e){super(),this._windowsWrappingHeuristics=this._register(new ot),this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onData=this._register(new b),this.onData=this._onData.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new b),this._instantiationService=new qh,this.optionsService=this._register(new tl(e)),this._instantiationService.setService(he,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(us)),this._instantiationService.setService(ce,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(ds)),this._instantiationService.setService(Gr,this._logService),this.coreService=this._register(this._instantiationService.createInstance(_s)),this._instantiationService.setService(je,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(fs)),this._instantiationService.setService(Xr,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(Qe)),this._instantiationService.setService(co,this.unicodeService),this._charsetService=this._instantiationService.createInstance(ol),this._instantiationService.setService(lo,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(vs),this._instantiationService.setService(Jr,this._oscLinkService),this._inputHandler=this._register(new wl(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(_e.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(_e.forward(this._bufferService.onResize,this._onResize)),this._register(_e.forward(this.coreService.onData,this._onData)),this._register(_e.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new Cl((t,i)=>this._inputHandler.parse(t,i))),this._register(_e.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new b),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!Nr&&(this._logService.warn("writeSync is unreliable and will be removed soon."),Nr=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,Hn),t=Math.max(t,Wn),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.buildNumber!==void 0&&t.buildNumber!==void 0&&(e=t.backend==="conpty"&&t.buildNumber<21376),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Dr.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>(Dr(this._bufferService),!1))),this._windowsWrappingHeuristics.value=q(()=>{for(let t of e)t.dispose()})}}},U=0,xl=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new oi,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new oi,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){let e=this._insertedValues.sort((r,n)=>this._getKey(r)-this._getKey(n)),t=0,i=0,s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),this._array.length===0)return!1;let t=this._getKey(e);if(t===void 0||(U=this._search(t),U===-1)||this._getKey(this._array[U])!==t)return!1;do if(this._array[U]===e)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(U),!0;while(++Ur-n),t=0,i=new Array(this._array.length-e.length),s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(U=this._search(e),!(U<0||U>=this._array.length)&&this._getKey(this._array[U])===e))do yield this._array[U];while(++U=this._array.length)&&this._getKey(this._array[U])===e))do t(this._array[U]);while(++U=t;){let s=t+i>>1,r=this._getKey(this._array[s]);if(r>e)i=s-1;else if(r0&&this._getKey(this._array[s-1])===e;)s--;return s}}return t}},Mi=0,Fr=0,Li=0,Hr=0,El=class extends I{constructor(){super(),this._decorations=new xl(e=>e?.marker.line),this._onDecorationRegistered=this._register(new b),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new b),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register(q(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new Bl(e);if(t){let i=t.marker.onDispose(()=>t.dispose()),s=t.onDispose(()=>{s.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),i.dispose())});this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,i){let s=0,r=0,n=0,o=0;for(let h of this._decorations.values())n=h.marker.line,o=n+(h.options.height??1),!(t=o)&&(s=h.options.x??0,r=s+(h.options.width??1),e>=s&&e=Hr)&&(Mi=r.options.x??0,Fr=Mi+(r.options.width??1),e>=Mi&&e=this._debounceThresholdMS)this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){let r=s-this._lastRefreshMs,n=this._debounceThresholdMS-r;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},n)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}},Wr=20,zr=!1,ai=class extends I{constructor(e,t,i,s){super(),this._terminal=e,this._coreBrowserService=i,this._renderService=s,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="";let r=this._coreBrowserService.mainDocument;this._accessibilityContainer=r.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=r.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let n=0;nthis._handleBoundaryFocus(n,0),this._bottomBoundaryFocusListener=n=>this._handleBoundaryFocus(n,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Rl(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");zr?(this._accessibilityContainer.classList.add("debug"),this._rowContainer.classList.add("debug"),this._debugRootContainer=r.createElement("div"),this._debugRootContainer.classList.add("xterm"),this._debugRootContainer.appendChild(r.createTextNode("------start a11y------")),this._debugRootContainer.appendChild(this._accessibilityContainer),this._debugRootContainer.appendChild(r.createTextNode("------end a11y------")),this._terminal.element.insertAdjacentElement("afterend",this._debugRootContainer)):this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(n=>this._handleResize(n.rows))),this._register(this._terminal.onRender(n=>this._refreshRows(n.start,n.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(n=>this._handleChar(n))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` -`))),this._register(this._terminal.onA11yTab(n=>this._handleTab(n))),this._register(this._terminal.onKey(n=>this._handleKey(n.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(B(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(q(()=>{zr?this._debugRootContainer.remove():this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` -`&&(this._liveRegionLineCount++,this._liveRegionLineCount===Wr+1&&(this._liveRegion.textContent=Zt.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){let n=i.lines.get(i.ydisp+r),o=[],h=n?.translateToString(!0,void 0,void 0,o)||"",l=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(h.length===0?(a.textContent="\xA0",this._rowColumns.set(a,[0,1])):(a.textContent=h,this._rowColumns.set(a,o)),a.setAttribute("aria-posinset",l),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent===Zt.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){let i=e.target,s=this._rowElements[t===0?1:this._rowElements.length-2],r=i.getAttribute("aria-posinset"),n=t===0?"1":`${this._terminal.buffer.lines.length}`;if(r===n||e.relatedTarget!==s)return;let o,h;if(t===0?(o=i,h=this._rowElements.pop(),this._rowContainer.removeChild(h)):(o=this._rowElements.shift(),h=i,this._rowContainer.removeChild(o)),o.removeEventListener("focus",this._topBoundaryFocusListener),h.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){let l=this._createAccessibilityTreeNode();this._rowElements.unshift(l),this._rowContainer.insertAdjacentElement("afterbegin",l)}else{let l=this._createAccessibilityTreeNode();this._rowElements.push(l),this._rowContainer.appendChild(l)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error("anchorNode and/or focusNode are null");return}let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;let r=({node:h,offset:l})=>{let a=h instanceof Text?h.parentNode:h,d=parseInt(a?.getAttribute("aria-posinset"),10)-1;if(isNaN(d))return console.warn("row is invalid. Race condition?"),null;let c=this._rowColumns.get(a);if(!c)return console.warn("columns is null. Race condition?"),null;let u=l=this._terminal.cols&&(++d,u=0),{row:d,column:u}},n=r(t),o=r(i);if(!(!n||!o)){if(n.row>o.row||n.row===o.row&&n.column>=o.column)throw new Error("invalid range");this._terminal.select(n.column,n.row,(o.row-n.row)*this._terminal.cols-n.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;te;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Ii(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(B(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(B(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(B(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(B(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;let i=e.composedPath();for(let s=0;s{s?.forEach(r=>{r.link.dispose&&r.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(let[s,r]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(s)&&(i=this._checkLinkProviderResult(s,e,i)):r.provideLinks(e.y,n=>{if(this._isMouseOut)return;let o=n?.map(h=>({link:h}));this._activeProviderReplies?.set(s,o),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let i=new Set;for(let s=0;se?this._bufferService.cols:o.link.range.end.x;for(let a=h;a<=l;a++){if(i.has(a)){r.splice(n--,1);break}i.add(a)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;let s=this._activeProviderReplies.get(e),r=!1;for(let n=0;nthis._linkAtPosition(o.link,t));n&&(i=!0,this._handleNewLink(n))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let n=0;nthis._linkAtPosition(h.link,t));if(o){i=!0,this._handleNewLink(o);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink&&Ml(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Ii(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0?!0:e.link.decorations.underline,pointerCursor:e.link.decorations===void 0?!0:e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:i=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==i&&(this._currentLink.state.decorations.pointerCursor=i,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",i))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:i=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==i&&(this._currentLink.state.decorations.underline=i,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,i))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(i=>{if(!this._currentLink)return;let s=i.start===0?0:i.start+1+this._bufferService.buffer.ydisp,r=this._bufferService.buffer.ydisp+1+i.end;if(this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=r&&(this._clearCurrentLink(s,r),this._lastMouseEvent)){let n=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);n&&this._askForLink(n,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){let i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){let i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t,i){let s=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(s)return{x:s[0],y:s[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};ms=K([w(1,ws),w(2,Ne),w(3,ce),w(4,Qr)],ms);function Ml(e,t){return e.text===t.text&&e.range.start.x===t.range.start.x&&e.range.start.y===t.range.start.y&&e.range.end.x===t.range.end.x&&e.range.end.y===t.range.end.y}var Ll=class extends kl{constructor(e={}){super(e),this._linkifier=this._register(new ot),this.browser=Dn,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new ot),this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new b),this.onKey=this._onKey.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new b),this.onBell=this._onBell.event,this._onFocus=this._register(new b),this._onBlur=this._register(new b),this._onA11yCharEmitter=this._register(new b),this._onA11yTabEmitter=this._register(new b),this._onWillOpen=this._register(new b),this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._setup(),this._decorationService=this._instantiationService.createInstance(El),this._instantiationService.setService(Tt,this._decorationService),this._keyboardService=this._instantiationService.createInstance(cs),this._instantiationService.setService(fo,this._keyboardService),this._linkProviderService=this._instantiationService.createInstance(mh),this._instantiationService.setService(Qr,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(Oi)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(t=>this.refresh(t?.start??0,t?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(t=>this._reportWindowsOptions(t))),this._register(this._inputHandler.onColor(t=>this._handleColorEvent(t))),this._register(_e.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(_e.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(_e.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(_e.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(t=>this._afterResize(t.cols,t.rows))),this._register(q(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}get dimensions(){if(!this._renderService)return;let e=this._renderService.dimensions;return{css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}}_handleColorEvent(e){if(this._themeService)for(let t of e){let i,s="";switch(t.index){case 256:i="foreground",s="10";break;case 257:i="background",s="11";break;case 258:i="cursor",s="12";break;default:i="ansi",s="4;"+t.index}switch(t.type){case 0:let r=N.toColorRGB(i==="ansi"?this._themeService.colors.ansi[t.index]:this._themeService.colors[i]);this.coreService.triggerDataEvent(`\x1B]${s};${vl(r)}\x1B\\`);break;case 1:if(i==="ansi")this._themeService.modifyColors(n=>n.ansi[t.index]=V.toColor(...t.color));else{let n=i;this._themeService.modifyColors(o=>o[n]=V.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_reportColorScheme(){if(!this._themeService)return;let e=oe.relativeLuminance(this._themeService.colors.background.rgba>>8),t=oe.relativeLuminance(this._themeService.colors.foreground.rgba>>8),i=e{this.hasSelection()&&so(t,this._selectionService)}));let e=t=>ro(t,this.textarea,this.coreService,this.optionsService);this._register(B(this.textarea,"paste",e)),this._register(B(this.element,"paste",e)),Rn?this._register(B(this.element,"mousedown",t=>{t.button===2&&Fs(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(B(this.element,"contextmenu",t=>{Fs(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),Ps&&this._register(B(this.element,"auxclick",t=>{t.button===1&&qr(t,this.textarea,this.screenElement)}))}_bindKeys(){this._register(B(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(B(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(B(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(B(this.textarea,"compositionstart",()=>this._compositionHelper.compositionstart())),this._register(B(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(B(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(B(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",r=>this.element.classList.toggle("allow-transparency",r))),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(B(this.screenElement,"mousemove",r=>this.updateCursorStyle(r))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",Ti.get()),Pn||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(gh,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<"u"?window.document:null)),this._instantiationService.setService(Ie,this._coreBrowserService),this._register(B(this.textarea,"focus",r=>this._handleTextAreaFocus(r))),this._register(B(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(ns,this._document,this._helperContainer),this._instantiationService.setService(hi,this._charSizeService),this._themeService=this._instantiationService.createInstance(ls),this._instantiationService.setService(ht,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(ni),this._instantiationService.setService(Zr,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(as,this.rows,this.screenElement)),this._instantiationService.setService(Ne,this._renderService),this._register(this._renderService.onRenderedViewportChange(r=>this._onRender.fire(r))),this._register(this._renderService.onDimensionsChange(r=>this._onDimensionsChange.fire({css:{canvas:{...r.css.canvas},cell:{...r.css.cell}},device:{canvas:{...r.device.canvas},cell:{...r.device.cell},char:{...r.device.char}}}))),this.onResize(r=>this._renderService.resize(r.cols,r.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(is,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(os),this._instantiationService.setService(ws,this._mouseService);let s=this._linkifier.value=this._register(this._instantiationService.createInstance(ms,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(es,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(r=>{super.scrollLines(r,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(hs,this.element,this.screenElement,s)),this._instantiationService.setService(_o,this._selectionService),this._register(this._selectionService.onRequestScrollLines(r=>this.scrollLines(r.amount,r.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(r=>this._renderService.handleSelectionChanged(r.start,r.end,r.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(r=>{this.textarea.value=r,this.textarea.focus(),this.textarea.select()})),this._register(_e.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(ts,this.screenElement)),this._register(B(this.element,"mousedown",r=>this._selectionService.handleMouseDown(r))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(ai,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",r=>this._handleScreenReaderModeOptionChange(r))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ri,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRuler",r=>{!this._overviewRulerRenderer&&r&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ri,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(rs,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,t=this.element;function i(n){let o=e._mouseService?.getMouseReportCoords(n,e.screenElement);if(!o)return!1;let h,l;switch(n.overrideType||n.type){case"mousemove":l=32,n.buttons===void 0?(h=3,n.button!==void 0&&(h=n.button<3?n.button:3)):h=n.buttons&1?0:n.buttons&4?1:n.buttons&2?2:3;break;case"mouseup":l=0,h=n.button<3?n.button:3;break;case"mousedown":l=1,h=n.button<3?n.button:3;break;case"wheel":if(e._customWheelEventHandler&&e._customWheelEventHandler(n)===!1)return!1;let a=n.deltaY;if(a===0||e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;l=a<0?0:1,h=4;break;default:return!1}return l===void 0||h===void 0||h>4?!1:e.coreMouseService.triggerMouseEvent({col:o.col,row:o.row,x:o.x,y:o.y,button:h,action:l,ctrl:n.ctrlKey,alt:n.altKey,shift:n.shiftKey})}let s={mouseup:null,wheel:null,mousedrag:null,mousemove:null},r={mouseup:n=>{i(n),n.buttons||(this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag))},wheel:n=>(i(n),n.preventDefault(),n.stopPropagation(),!1),mousedrag:n=>{n.buttons&&i(n)},mousemove:n=>{n.buttons||i(n)}};this._register(this.coreMouseService.onProtocolChange(n=>{n?(this.optionsService.rawOptions.logLevel==="debug"&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(n)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),n&8?s.mousemove||(t.addEventListener("mousemove",r.mousemove),s.mousemove=r.mousemove):(t.removeEventListener("mousemove",s.mousemove),s.mousemove=null),n&16?s.wheel||(t.addEventListener("wheel",r.wheel,{passive:!1}),s.wheel=r.wheel):(t.removeEventListener("wheel",s.wheel),s.wheel=null),n&2?s.mouseup??(s.mouseup=r.mouseup):(this._document.removeEventListener("mouseup",s.mouseup),s.mouseup=null),n&4?s.mousedrag??(s.mousedrag=r.mousedrag):(this._document.removeEventListener("mousemove",s.mousedrag),s.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(q(()=>{s.mouseup&&this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag)})),this._register(B(t,"mousedown",n=>{n.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(n))&&(i(n),s.mouseup&&this._document.addEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.addEventListener("mousemove",s.mousedrag))})),this._register(B(t,"wheel",n=>{if(!s.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(n)===!1)return!1;if(!this.buffer.hasScrollback){if(n.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return n.preventDefault(),n.stopPropagation(),!1;let o="\x1B"+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(n.deltaY<0?"A":"B");return this.coreService.triggerDataEvent(o,!0),n.preventDefault(),n.stopPropagation(),!1}}},{passive:!1}))}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){Ur(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),i.type===3||i.type===2){let r=this.rows-1;return this.scrollLines(i.type===2?-r:r),e.preventDefault(),e.stopPropagation(),!1}if(i.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)||!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;(i.key===""||i.key==="\r")&&(this.textarea.value="");let s=this._keyboardService.useWin32InputMode&&Pi(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){let i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return t.type==="keypress"?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return;Pi(e)||this.focus();let t=this._keyboardService.evaluateKeyUp(e);if(t?.key){let i=this._keyboardService.useWin32InputMode&&Pi(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType==="insertText"&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(!(this.buffer.ybase===0&&this.buffer.y===0)){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){let i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new ke)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}},Kr=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){let t=this._buffer.lines.get(e);if(t)return new Tl(t)}getNullCell(){return new ke}},Al=class extends I{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new b),this.onBufferChange=this._onBufferChange.event,this._normal=new Kr(this._core.buffers.normal,"normal"),this._alternate=new Kr(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}},Ol=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,i=>t(i.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(i,s)=>t(i,s.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}},Il=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}},Nl=["cols","rows"],De=0,jn=class extends I{constructor(e){super(),this._core=this._register(new Ll(e)),this._addonManager=this._register(new Pl),this._publicOptions={...this._core.options};let t=s=>this._core.options[s],i=(s,r)=>{this._checkReadonlyOptions(s),this._core.options[s]=r};for(let s in this._core.options){let r={get:t.bind(this,s),set:i.bind(this,s)};Object.defineProperty(this._publicOptions,s,r)}}_checkReadonlyOptions(e){if(Nl.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get parser(){return this._parser??(this._parser=new Ol(this._core))}get unicode(){return this._checkProposedApi(),new Il(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??(this._buffer=this._register(new Al(this._core)))}get markers(){return this._core.markers}get modes(){let e=this._core.coreService.decPrivateModes,t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any";break}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(let t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write(`\r -`,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return Ti.get()},set promptLabel(e){Ti.set(e)},get tooMuchOutput(){return Zt.get()},set tooMuchOutput(e){Zt.set(e)}}}_verifyIntegers(...e){for(De of e)if(De===1/0||isNaN(De)||De%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(De of e)if(De&&(De===1/0||isNaN(De)||De%1!==0||De<0))throw new Error("This API only accepts positive integers")}};/** +`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),As&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s||!t?!1:this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){let i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s?!1:this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=pr(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let s=this._getMouseBufferCoords(e);return s?(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=Ps(this._coreBrowserService.window,e,this._screenElement)[1],i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-Ei),Ei),t/=Ei,t/Math.abs(t)+Math.round(t*(Fh-1)))}shouldForceSelection(e){return Rt?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),Hh)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){let t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);let i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&i.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(Rt&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&tthis._handleTrim(t))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){let r=e.loadCell(s,this._workCell).getChars().length;this._workCell.getWidth()===0?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;let r=this._bufferService.buffer,n=r.lines.get(e[1]);if(!n)return;let o=r.translateBufferLineToString(e[1],!1),h=this._convertViewportColToCharacterIndex(n,e[0]),l=h,a=e[0]-h,d=0,c=0,u=0,f=0;if(o.charAt(h)===" "){for(;h>0&&o.charAt(h-1)===" ";)h--;for(;l1&&(f+=A-1,l+=A-1);y>0&&h>0&&!this._isCharWordSeparator(n.loadCell(y-1,this._workCell));){n.loadCell(y-1,this._workCell);let R=this._workCell.getChars().length;this._workCell.getWidth()===0?(d++,y--):R>1&&(u+=R-1,h-=R-1),h--,y--}for(;E1&&(f+=R-1,l+=R-1),l++,E++}}l++;let _=h+a-d+u,p=Math.min(this._bufferService.cols,l-h+d+c-u-f);if(!(!t&&o.slice(h,l).trim()==="")){if(i&&_===0&&n.getCodePoint(0)!==32){let y=r.lines.get(e[1]-1);if(y&&n.isWrapped&&y.getCodePoint(this._bufferService.cols-1)!==32){let E=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(E){let A=this._bufferService.cols-E.start;_-=A,p+=A}}}if(s&&_+p===this._bufferService.cols&&n.getCodePoint(this._bufferService.cols-1)!==32){let y=r.lines.get(e[1]+1);if(y?.isWrapped&&y.getCodePoint(0)!==32){let E=this._getWordAt([0,e[1]+1],!1,!1,!0);E&&(p+=E.length)}}return{start:_,length:p}}}_selectWordAt(e,t){let i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return e.getWidth()===0?!1:this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=pr(i,this._bufferService.cols)}};cs=K([w(3,ce),w(4,Xe),w(5,ys),w(6,he),w(7,Fe),w(8,Ne)],cs);var gr=class{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}},vr=class{constructor(){this._color=new gr,this._css=new gr}setCss(e,t,i){this._css.set(e,t,i)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,i){this._color.set(e,t,i)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}},Y=Object.freeze((()=>{let e=[z.toColor("#2e3436"),z.toColor("#cc0000"),z.toColor("#4e9a06"),z.toColor("#c4a000"),z.toColor("#3465a4"),z.toColor("#75507b"),z.toColor("#06989a"),z.toColor("#d3d7cf"),z.toColor("#555753"),z.toColor("#ef2929"),z.toColor("#8ae234"),z.toColor("#fce94f"),z.toColor("#729fcf"),z.toColor("#ad7fa8"),z.toColor("#34e2e2"),z.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){let s=t[i/36%6|0],r=t[i/6%6|0],n=t[i%6];e.push({css:V.toCss(s,r,n),rgba:V.toRgba(s,r,n)})}for(let i=0;i<24;i++){let s=8+i*10;e.push({css:V.toCss(s,s,s),rgba:V.toRgba(s,s,s)})}return e})()),Ze=z.toColor("#ffffff"),Ct=z.toColor("#000000"),mr=z.toColor("#ffffff"),Sr=Ct,vt={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},$h=Ze,ds=class extends I{constructor(e){super(),this._optionsService=e,this._contrastCache=new vr,this._halfContrastCache=new vr,this._onChangeColors=this._register(new b),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:Ze,background:Ct,cursor:mr,cursorAccent:Sr,selectionForeground:void 0,selectionBackgroundTransparent:vt,selectionBackgroundOpaque:N.blend(Ct,vt),selectionInactiveBackgroundTransparent:vt,selectionInactiveBackgroundOpaque:N.blend(Ct,vt),scrollbarSliderBackground:N.opacity(Ze,.2),scrollbarSliderHoverBackground:N.opacity(Ze,.4),scrollbarSliderActiveBackground:N.opacity(Ze,.5),overviewRulerBorder:Ze,ansi:Y.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=O(e.foreground,Ze),t.background=O(e.background,Ct),t.cursor=N.blend(t.background,O(e.cursor,mr)),t.cursorAccent=N.blend(t.background,O(e.cursorAccent,Sr)),t.selectionBackgroundTransparent=O(e.selectionBackground,vt),t.selectionBackgroundOpaque=N.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=O(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=N.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?O(e.selectionForeground,ur):void 0,t.selectionForeground===ur&&(t.selectionForeground=void 0),N.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=N.opacity(t.selectionBackgroundTransparent,.3)),N.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=N.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=O(e.scrollbarSliderBackground,N.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=O(e.scrollbarSliderHoverBackground,N.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=O(e.scrollbarSliderActiveBackground,N.opacity(t.foreground,.5)),t.overviewRulerBorder=O(e.overviewRulerBorder,$h),t.ansi=Y.slice(),t.ansi[0]=O(e.black,Y[0]),t.ansi[1]=O(e.red,Y[1]),t.ansi[2]=O(e.green,Y[2]),t.ansi[3]=O(e.yellow,Y[3]),t.ansi[4]=O(e.blue,Y[4]),t.ansi[5]=O(e.magenta,Y[5]),t.ansi[6]=O(e.cyan,Y[6]),t.ansi[7]=O(e.white,Y[7]),t.ansi[8]=O(e.brightBlack,Y[8]),t.ansi[9]=O(e.brightRed,Y[9]),t.ansi[10]=O(e.brightGreen,Y[10]),t.ansi[11]=O(e.brightYellow,Y[11]),t.ansi[12]=O(e.brightBlue,Y[12]),t.ansi[13]=O(e.brightMagenta,Y[13]),t.ansi[14]=O(e.brightCyan,Y[14]),t.ansi[15]=O(e.brightWhite,Y[15]),e.extendedAnsi){let i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;s"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};function qh(e,t,i,s){let r={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:e.key==="UIKeyInputUpArrow"?t?r.key="\x1BOA":r.key="\x1B[A":e.key==="UIKeyInputLeftArrow"?t?r.key="\x1BOD":r.key="\x1B[D":e.key==="UIKeyInputRightArrow"?t?r.key="\x1BOC":r.key="\x1B[C":e.key==="UIKeyInputDownArrow"&&(t?r.key="\x1BOB":r.key="\x1B[B");break;case 8:r.key=e.ctrlKey?"\b":"\x7F",e.altKey&&(r.key="\x1B"+r.key);break;case 9:if(e.shiftKey){r.key="\x1B[Z";break}r.key=" ",r.cancel=!0;break;case 13:r.key=e.altKey?"\x1B\r":"\r",r.cancel=!0;break;case 27:r.key="\x1B",e.altKey&&(r.key="\x1B\x1B"),r.cancel=!0;break;case 37:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"D":t?r.key="\x1BOD":r.key="\x1B[D";break;case 39:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"C":t?r.key="\x1BOC":r.key="\x1B[C";break;case 38:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"A":t?r.key="\x1BOA":r.key="\x1B[A";break;case 40:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"B":t?r.key="\x1BOB":r.key="\x1B[B";break;case 45:!e.shiftKey&&!e.ctrlKey&&(r.key="\x1B[2~");break;case 46:n?r.key="\x1B[3;"+(n+1)+"~":r.key="\x1B[3~";break;case 36:n?r.key="\x1B[1;"+(n+1)+"H":t?r.key="\x1BOH":r.key="\x1B[H";break;case 35:n?r.key="\x1B[1;"+(n+1)+"F":t?r.key="\x1BOF":r.key="\x1B[F";break;case 33:e.shiftKey?r.type=2:e.ctrlKey?r.key="\x1B[5;"+(n+1)+"~":r.key="\x1B[5~";break;case 34:e.shiftKey?r.type=3:e.ctrlKey?r.key="\x1B[6;"+(n+1)+"~":r.key="\x1B[6~";break;case 112:n?r.key="\x1B[1;"+(n+1)+"P":r.key="\x1BOP";break;case 113:n?r.key="\x1B[1;"+(n+1)+"Q":r.key="\x1BOQ";break;case 114:n?r.key="\x1B[1;"+(n+1)+"R":r.key="\x1BOR";break;case 115:n?r.key="\x1B[1;"+(n+1)+"S":r.key="\x1BOS";break;case 116:n?r.key="\x1B[15;"+(n+1)+"~":r.key="\x1B[15~";break;case 117:n?r.key="\x1B[17;"+(n+1)+"~":r.key="\x1B[17~";break;case 118:n?r.key="\x1B[18;"+(n+1)+"~":r.key="\x1B[18~";break;case 119:n?r.key="\x1B[19;"+(n+1)+"~":r.key="\x1B[19~";break;case 120:n?r.key="\x1B[20;"+(n+1)+"~":r.key="\x1B[20~";break;case 121:n?r.key="\x1B[21;"+(n+1)+"~":r.key="\x1B[21~";break;case 122:n?r.key="\x1B[23;"+(n+1)+"~":r.key="\x1B[23~";break;case 123:n?r.key="\x1B[24;"+(n+1)+"~":r.key="\x1B[24~";break;default:if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&!e.metaKey)e.keyCode>=65&&e.keyCode<=90?r.key=String.fromCharCode(e.keyCode-64):e.keyCode===32?r.key="\0":e.keyCode>=51&&e.keyCode<=55?r.key=String.fromCharCode(e.keyCode-51+27):e.keyCode===56?r.key="\x7F":e.key==="/"?r.key="":e.keyCode===219?r.key="\x1B":e.keyCode===220?r.key="":e.keyCode===221&&(r.key="");else if((!i||s)&&e.altKey&&!e.metaKey){let o=Uh[e.keyCode]?.[e.shiftKey?1:0];if(o)r.key="\x1B"+o;else if(e.keyCode>=65&&e.keyCode<=90){let h=e.ctrlKey?e.keyCode-64:e.keyCode+32,l=String.fromCharCode(h);e.shiftKey&&(l=l.toUpperCase()),r.key="\x1B"+l}else if(e.keyCode===32)r.key="\x1B"+(e.ctrlKey?"\0":" ");else if(e.key==="Dead"&&e.code.startsWith("Key")){let h=e.code.slice(3,4);e.shiftKey||(h=h.toLowerCase()),r.key="\x1B"+h,r.cancel=!0}}else if(i&&!e.altKey&&!e.ctrlKey&&!e.shiftKey&&e.metaKey)e.keyCode===65&&(r.type=1);else if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&e.key.length===1)r.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":r.key="";break;case"Digit2":r.key="\0";break;case"Digit6":r.key="";break}break}return r}var wr=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){let t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e){let t=this._getNumpadKeyCode(e);if(t!==void 0)return t;let i=this._getModifierKeyCode(e);if(i!==void 0)return i;let s=this._functionalKeyCodes[e.key];if(s!==void 0)return s;if(e.shiftKey&&e.code){if(e.code.startsWith("Digit")&&e.code.length===6){let r=e.code.charAt(5);if(r>="0"&&r<="9")return r.charCodeAt(0)}if(e.code.startsWith("Key")&&e.code.length===4)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(e.key.length===1){let r=e.key.codePointAt(0);return r>=65&&r<=90?r+32:r}}_isModifierKey(e){return e.key==="Shift"||e.key==="Control"||e.key==="Alt"||e.key==="Meta"}_buildCsiLetterSequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1B["+e}_buildSs3Sequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1BO"+e}_buildCsiTildeSequence(e,t,i,s){let r=s&&i!==1,n="\x1B["+e;return(t>0||r)&&(n+=";"+(t>0?t:"1"),r&&(n+=":"+i)),n+="~",n}_buildCsiUSequence(e,t,i,s,r,n,o){let h=!!(r&2),l=!!(r&4),a="\x1B["+t,d;l&&e.shiftKey&&e.key.length===1&&!n&&!o&&(d=e.key.codePointAt(0),a+=":"+d);let c=r&16&&s!==3&&e.key.length===1&&!n&&!o&&!e.ctrlKey?e.key.codePointAt(0):void 0,u=h&&s!==1&&(s===3||c===void 0);return(i>0||u||c!==void 0)&&(a+=";",i>0?a+=i:u&&(a+="1"),u&&(a+=":"+s)),c!==void 0&&(a+=";"+c),a+="u",a}evaluate(e,t,i=1){let s={type:0,cancel:!1,key:void 0},r=this._encodeModifiers(e),n=this._isModifierKey(e),o=!!(t&2);if(!o&&i===3||n&&!(t&8)&&!o)return s;let h=this._csiLetterKeys[e.key];if(h)return s.key=this._buildCsiLetterSequence(h,r,i,o),s.cancel=!0,s;let l=this._ss3FunctionKeys[e.key];if(l)return s.key=this._buildSs3Sequence(l,r,i,o),s.cancel=!0,s;let a=this._csiTildeKeys[e.key];if(a!==void 0)return s.key=this._buildCsiTildeSequence(a,r,i,o),s.cancel=!0,s;let d=this._getKeyCode(e);if(d===void 0)return s;let c=this._functionalKeyCodes[e.key]!==void 0||this._getNumpadKeyCode(e)!==void 0,u=!1;return t&8||o?u=!0:t&1&&(d===27||d===127||d===13||d===9||d===32||c?u=!0:r>0&&(e.shiftKey&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1?u=!1:u=!0)),u?(s.key=this._buildCsiUSequence(e,d,r,i,t,c,n),s.cancel=!0):e.key.length===1&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&(s.key=e.key),s}static shouldUseProtocol(e){return e>0}},Vh=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){let t=this._codeToVk[e.code];return t!==void 0?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(e.key==="Enter")return 10;if(e.key==="Backspace")return 127}let t=this._keyToControlChar[e.key];if(t!==void 0)return t;if(e.key.length===1){let i=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(i>=65&&i<=90)return i-64;if(i>=97&&i<=122)return i-96}return i}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&(e.code==="ControlRight"?t|=4:t|=8),e.altKey&&(e.code==="AltRight"?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){let i=this._getVirtualKeyCode(e),s=this._getScanCode(e),r=this._getUnicodeChar(e),n=t?1:0,o=this._getControlKeyState(e);return{type:0,cancel:!0,key:`\x1B[${i};${s};${r};${n};${o};1_`}}},us=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??(this._win32InputMode=new Vh),this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??(this._kittyKeyboard=new wr),this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);let t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1):qh(e,this._coreService.decPrivateModes.applicationCursorKeys,Rt,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);let t=this._coreService.kittyKeyboard.flags;if(this.useKitty&&t&2)return this._getKittyKeyboard().evaluate(e,t,3)}get useKitty(){let e=this._coreService.kittyKeyboard.flags;return!!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard&&wr.shouldUseProtocol(e))}get useWin32InputMode(){return!!(this._optionsService.rawOptions.vtExtensions?.win32InputMode&&this._coreService.decPrivateModes.win32InputMode)}};us=K([w(0,Xe),w(1,he)],us);var Yh=class{constructor(...e){this._entries=new Map;for(let[t,i]of e)this.set(t,i)}set(e,t){let i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(let[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}},jh=class{constructor(){this._services=new Yh,this._services.set(bs,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){let i=co(e).sort((n,o)=>n.index-o.index),s=[];for(let n of i){let o=this._services.get(n.id);if(!o)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${n.id._id}.`);s.push(o)}let r=i.length>0?i[0].index:t.length;if(t.length!==r)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${r+1} conflicts with ${t.length} static arguments`);return new e(...t,...s)}},Xh={trace:0,debug:1,info:2,warn:3,error:4,off:5},Gh="xterm.js: ",_s=class extends I{constructor(e){super(),this._optionsService=e,this._logLevel=5,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=Xh[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;s--)this._array[this._getCyclicIndex(s+i.length)]=this._array[this._getCyclicIndex(s)];for(let s=0;sthis._maxLength){let s=this._length+i.length-this._maxLength;this._startIndex+=s,this._length=this._maxLength,this.onTrimEmitter.fire(s)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let r=t-1;r>=0;r--)this.set(e+r+i,this.get(e+r));let s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>22,i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):s]}set(t,i){this._data[t*D+1]=i[0],i[1].length>1?(this._combined[t]=i[1],this._data[t*D+0]=t|2097152|i[2]<<22):this._data[t*D+0]=i[1].charCodeAt(0)|i[2]<<22}getWidth(t){return this._data[t*D+0]>>22}hasWidth(t){return this._data[t*D+0]&12582912}getFg(t){return this._data[t*D+1]}getBg(t){return this._data[t*D+2]}hasContent(t){return this._data[t*D+0]&4194303}getCodePoint(t){let i=this._data[t*D+0];return i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):i&2097151}isCombined(t){return this._data[t*D+0]&2097152}getString(t){let i=this._data[t*D+0];return i&2097152?this._combined[t]:i&2097151?Ye(i&2097151):""}isProtected(t){return this._data[t*D+2]&536870912}loadCell(t,i){return $t=t*D,i.content=this._data[$t+0],i.fg=this._data[$t+1],i.bg=this._data[$t+2],i.content&2097152&&(i.combinedData=this._combined[t]),i.bg&268435456&&(i.extended=this._extendedAttrs[t]),i}setCell(t,i){i.content&2097152&&(this._combined[t]=i.combinedData),i.bg&268435456&&(this._extendedAttrs[t]=i.extended),this._data[t*D+0]=i.content,this._data[t*D+1]=i.fg,this._data[t*D+2]=i.bg}setCellFromCodepoint(t,i,s,r){r.bg&268435456&&(this._extendedAttrs[t]=r.extended),this._data[t*D+0]=i|s<<22,this._data[t*D+1]=r.fg,this._data[t*D+2]=r.bg}addCodepointToCell(t,i,s){let r=this._data[t*D+0];r&2097152?this._combined[t]+=Ye(i):r&2097151?(this._combined[t]=Ye(r&2097151)+Ye(i),r&=-2097152,r|=2097152):r=i|1<<22,s&&(r&=-12582913,r|=s<<22),this._data[t*D+0]=r}insertCells(t,i,s){if(t%=this.length,t&&this.getWidth(t-1)===2&&this.setCellFromCodepoint(t-1,0,1,s),i=0;--r)this.setCell(t+i+r,this.loadCell(t+r,yr));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=s*4)this._data=new Uint32Array(this._data.buffer,0,s);else{let r=new Uint32Array(s);r.set(this._data),this._data=r}for(let r=this.length;r=t&&delete this._combined[h]}let n=Object.keys(this._extendedAttrs);for(let o=0;o=t&&delete this._extendedAttrs[h]}}return this.length=t,s*4*Bi=0;--t)if(this._data[t*D+0]&4194303)return t+(this._data[t*D+0]>>22);return 0}getNoBgTrimmedLength(){for(let t=this.length-1;t>=0;--t)if(this._data[t*D+0]&4194303||this._data[t*D+2]&50331648)return t+(this._data[t*D+0]>>22);return 0}copyCellsFrom(t,i,s,r,n){let o=t._data;if(n)for(let l=r-1;l>=0;l--){for(let a=0;a=i&&(this._combined[a-i+s]=t._combined[a])}}translateToString(t,i,s,r){i=i??0,s=s??this.length,t&&(s=Math.min(s,this.getTrimmedLength())),r&&(r.length=0);let n="";for(;i>22||1}return r&&r.push(i),n}};function Jh(e,t,i,s,r,n){let o=[];for(let h=0;h=h&&s0&&(y>c||d[y].getTrimmedLength()===0);y--)p++;p>0&&(o.push(h+d.length-p),o.push(p)),h+=d.length-1}return o}function Zh(e,t){let i=[],s=0,r=t[s],n=0;for(let o=0;oPt(e,a,t)).reduce((l,a)=>l+a),n=0,o=0,h=0;for(;hl&&(n-=l,o++);let a=e[o].getWidth(n-1)===2;a&&n--;let d=a?i-1:i;s.push(d),h+=d}return s}function Pt(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();let s=!e[t].hasContent(i-1)&&e[t].getWidth(i-1)===1,r=e[t+1].getWidth(0)===2;return s&&r?i-1:i}var Hn=class Wn{constructor(t){this.line=t,this.isDisposed=!1,this._disposables=[],this._id=Wn._nextId++,this._onDispose=this.register(new b),this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Fi(this._disposables),this._disposables.length=0)}register(t){return this._disposables.push(t),t}};Hn._nextId=1;var tl=Hn,X={},Qe=X.B;X[0]={"`":"\u25C6",a:"\u2592",b:"\u2409",c:"\u240C",d:"\u240D",e:"\u240A",f:"\xB0",g:"\xB1",h:"\u2424",i:"\u240B",j:"\u2518",k:"\u2510",l:"\u250C",m:"\u2514",n:"\u253C",o:"\u23BA",p:"\u23BB",q:"\u2500",r:"\u23BC",s:"\u23BD",t:"\u251C",u:"\u2524",v:"\u2534",w:"\u252C",x:"\u2502",y:"\u2264",z:"\u2265","{":"\u03C0","|":"\u2260","}":"\xA3","~":"\xB7"};X.A={"#":"\xA3"};X.B=void 0;X[4]={"#":"\xA3","@":"\xBE","[":"ij","\\":"\xBD","]":"|","{":"\xA8","|":"f","}":"\xBC","~":"\xB4"};X.C=X[5]={"[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X.R={"#":"\xA3","@":"\xE0","[":"\xB0","\\":"\xE7","]":"\xA7","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xA8"};X.Q={"@":"\xE0","[":"\xE2","\\":"\xE7","]":"\xEA","^":"\xEE","`":"\xF4","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xFB"};X.K={"@":"\xA7","[":"\xC4","\\":"\xD6","]":"\xDC","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xDF"};X.Y={"#":"\xA3","@":"\xA7","[":"\xB0","\\":"\xE7","]":"\xE9","`":"\xF9","{":"\xE0","|":"\xF2","}":"\xE8","~":"\xEC"};X.E=X[6]={"@":"\xC4","[":"\xC6","\\":"\xD8","]":"\xC5","^":"\xDC","`":"\xE4","{":"\xE6","|":"\xF8","}":"\xE5","~":"\xFC"};X.Z={"#":"\xA3","@":"\xA7","[":"\xA1","\\":"\xD1","]":"\xBF","{":"\xB0","|":"\xF1","}":"\xE7"};X.H=X[7]={"@":"\xC9","[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X["="]={"#":"\xF9","@":"\xE0","[":"\xE9","\\":"\xE7","]":"\xEA","^":"\xEE",_:"\xE8","`":"\xF4","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xFB"};var Cr=4294967295,kr=class{constructor(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=Q.clone(),this.savedCharset=Qe,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=ke.fromCharData([0,"",1,0]),this._whitespaceCell=ke.fromCharData([0," ",1,32]),this._isClearing=!1,this._memoryCleanupQueue=new hi,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new br(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new ti),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new ti),this._whitespaceCell}getBlankLine(e,t){return new kt(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&eCr?Cr:t}fillViewportRows(e){if(this.lines.length===0){e??(e=Q);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new br(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let i=this.getNullCell(Q),s=0,r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+n+1?(this.ybase--,n++,this.ydisp>0&&this.ydisp--):this.lines.push(new kt(e,i)));else for(let o=this._rows;o>t;o--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(o),this.ybase=Math.max(this.ybase-o,0),this.ydisp=Math.max(this.ydisp-o,0),this.savedY=Math.max(this.savedY-o,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),n&&(this.y+=n),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let n=0;n0){let n=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,n)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend==="conpty"&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let i=this._optionsService.rawOptions.reflowCursorLine,s=Jh(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(Q),i);if(s.length>0){let r=Zh(this.lines,s);Qh(this.lines,r.layout),this._reflowLargerAdjustViewport(e,t,r.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){let s=this.getNullCell(Q),r=i;for(;r-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;o--){let h=this.lines.get(o);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;let l=[h];for(;h.isWrapped&&o>0;)h=this.lines.get(--o),l.unshift(h);if(!i){let R=this.ybase+this.y;if(R>=o&&R0&&(r.push({start:o+l.length+n,newLines:f}),n+=f.length),l.push(...f);let _=d.length-1,p=d[_];p===0&&(_--,p=d[_]);let y=l.length-c-1,E=a;for(;y>=0;){let R=Math.min(E,p);if(l[_]===void 0)break;if(l[_].copyCellsFrom(l[y],E-R,p-R,R,!0),p-=R,p===0&&(_--,p=d[_]),E-=R,E===0){y--;let x=Math.max(y,0);E=Pt(l,x,this._cols)}}for(let R=0;R0;)this.ybase===0?this.y0){let o=[],h=[];for(let p=0;p=0;p--)if(c&&c.start>a+u){for(let y=c.newLines.length-1;y>=0;y--)this.lines.set(p--,c.newLines[y]);p++,o.push({index:a+1,amount:c.newLines.length}),u+=c.newLines.length,c=r[++d]}else this.lines.set(p,h[a--]);let f=0;for(let p=o.length-1;p>=0;p--)o[p].index+=f,this.lines.onInsertEmitter.fire(o[p]),f+=o[p].amount;let _=Math.max(0,l+n-this.lines.maxLength);_>0&&this.lines.onTrimEmitter.fire(_)}}translateBufferLineToString(e,t,i=0,s){let r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=i,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(i=>{t.line>=i.index&&(t.line+=i.amount)})),t.register(this.lines.onDelete(i=>{t.line>=i.index&&t.linei.index&&(t.line-=i.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}},il=class extends I{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new b),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new kr(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new kr(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}},zn=2,Kn=1,fs=class extends I{constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,zn),this.rows=Math.max(e.rawOptions.rows||0,Kn),this.buffers=this._register(new il(e,this)),this._register(this.buffers.onBufferActivate(t=>{this._onScroll.fire(t.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let i=this.buffer,s;s=this._cachedBlankLine,(!s||s.length!==this.cols||s.getFg(0)!==e.fg||s.getBg(0)!==e.bg)&&(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;let r=i.ybase+i.scrollTop,n=i.ybase+i.scrollBottom;if(i.scrollTop===0){let o=i.lines.isFull;n===i.lines.length-1?o?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(n+1,0,s.clone()),o?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{let o=n-r+1;i.lines.shiftElements(r+1,o-1,-1),i.lines.set(n,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){let i=this.buffer;if(e<0){if(i.ydisp===0)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);let s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};fs=K([w(0,he)],fs);var rt={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:Rt,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",overviewRuler:{},quirks:{},vtExtensions:{}},sl=["normal","bold","100","200","300","400","500","600","700","800","900"],rl=class extends I{constructor(e){super(),this._onOptionChange=this._register(new b),this.onOptionChange=this._onOptionChange.event;let t={...rt};for(let i in e)if(i in t)try{let s=e[i];t[i]=this._sanitizeAndValidateOption(i,s)}catch(s){console.error(s)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(q(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{e.indexOf(i)!==-1&&t()})}_setupOptions(){let e=i=>{if(!(i in rt))throw new Error(`No option with key "${i}"`);return this.rawOptions[i]},t=(i,s)=>{if(!(i in rt))throw new Error(`No option with key "${i}"`);s=this._sanitizeAndValidateOption(i,s),this.rawOptions[i]!==s&&(this.rawOptions[i]=s,this._onOptionChange.fire(i))};for(let i in this.rawOptions){let s={get:e.bind(this,i),set:t.bind(this,i)};Object.defineProperty(this.options,i,s)}}_sanitizeAndValidateOption(e,t){switch(e){case"cursorStyle":if(t||(t=rt[e]),!nl(t))throw new Error(`"${t}" is not a valid value for ${e}`);break;case"wordSeparator":t||(t=rt[e]);break;case"fontWeight":case"fontWeightBold":if(typeof t=="number"&&1<=t&&t<=1e3)break;t=sl.includes(t)?t:rt[e];break;case"cursorWidth":t=Math.floor(t);case"lineHeight":case"tabStopWidth":if(t<1)throw new Error(`${e} cannot be less than 1, value: ${t}`);break;case"minimumContrastRatio":t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case"scrollback":if(t=Math.min(t,4294967295),t<0)throw new Error(`${e} cannot be less than 0, value: ${t}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(t<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case"rows":case"cols":if(!t&&t!==0)throw new Error(`${e} must be numeric, value: ${t}`);break;case"windowsPty":t=t??{};break}return t}};function nl(e){return e==="block"||e==="underline"||e==="bar"}function xt(e,t=5){if(typeof e!="object")return e;let i=Array.isArray(e)?[]:{};for(let s in e)i[s]=t<=1?e[s]:e[s]&&xt(e[s],t-1);return i}var xr=Object.freeze({insertMode:!1}),Er=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0}),Br=()=>({flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}),ps=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new b),this.onData=this._onData.event,this._onUserInput=this._register(new b),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new b),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=xt(xr),this.decPrivateModes=xt(Er),this.kittyKeyboard=Br()}reset(){this.modes=xt(xr),this.decPrivateModes=xt(Er),this.kittyKeyboard=Br()}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(s=>s.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(t=>t.charCodeAt(0))),this._onBinary.fire(e))}};ps=K([w(0,ce),w(1,Zr),w(2,he)],ps);var Dr={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>e.button===4||e.action!==1?!1:(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>e.action!==32},DRAG:{events:23,restrict:e=>!(e.action===32&&e.button===3)},ANY:{events:31,restrict:e=>!0}};function Di(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return e.button===4?(i|=64,i|=e.action):(i|=e.button&3,e.button&4&&(i|=64),e.button&8&&(i|=128),e.action===32?i|=32:e.action===0&&!t&&(i|=3)),i}var Ri=String.fromCharCode,Rr={DEFAULT:e=>{let t=[Di(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`\x1B[M${Ri(t[0])}${Ri(t[1])}${Ri(t[2])}`},SGR:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Di(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Di(e,!0)};${e.x};${e.y}${t}`}},gs=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._coreService=t,this._optionsService=i,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new b),this.onProtocolChange=this._onProtocolChange.event;for(let s of Object.keys(Dr))this.addProtocol(s,Dr[s]);for(let s of Object.keys(Rr))this.addEncoding(s,Rr[s]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,i){if(e.deltaY===0||e.shiftKey||t===void 0||i===void 0)return 0;let s=t/i,r=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(r/=s+0,Math.abs(e.deltaY)<50&&(r*=.3),this._wheelPartialScroll+=r,r=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows||e.button===4&&e.action===32||e.button===3&&e.action!==32||e.button!==4&&(e.action===2||e.action===3)||(e.col++,e.row++,e.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._activeEncoding==="SGR_PIXELS"))||!this._protocols[this._activeProtocol].restrict(e))return!1;let t=this._encodings[this._activeEncoding](e);return t&&(this._activeEncoding==="DEFAULT"?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(e&1),up:!!(e&2),drag:!!(e&4),move:!!(e&8),wheel:!!(e&16)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x||e.y!==t.y)return!1}else if(e.col!==t.col||e.row!==t.row)return!1;return!(e.button!==t.button||e.action!==t.action||e.ctrl!==t.ctrl||e.alt!==t.alt||e.shift!==t.shift)}};gs=K([w(0,ce),w(1,Xe),w(2,he)],gs);var Mi=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],ol=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],j;function al(e,t){let i=0,s=t.length-1,r;if(et[s][1])return!1;for(;s>=i;)if(r=i+s>>1,e>t[r][1])i=r+1;else if(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),s=i===0&&t!==0;if(s){let r=et.extractWidth(t);r===0?s=!1:r>i&&(i=r)}return et.createPropertyValue(0,i,s)}},et=class Qt{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new b,this.onChange=this._onChange.event;let t=new hl;this.register(t),this._active=t.version,this._activeProvider=t}static extractShouldJoin(t){return(t&1)!==0}static extractWidth(t){return t>>1&3}static extractCharKind(t){return t>>3}static createPropertyValue(t,i,s=!1){return(t&16777215)<<3|(i&3)<<1|(s?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(t){if(!this._providers[t])throw new Error(`unknown Unicode version "${t}"`);this._active=t,this._activeProvider=this._providers[t],this._onChange.fire(t)}register(t){this._providers[t.version]=t}wcwidth(t){return this._activeProvider.wcwidth(t)}getStringCellWidth(t){let i=0,s=0,r=t.length;for(let n=0;n=r)return i+this.wcwidth(o);let a=t.charCodeAt(n);56320<=a&&a<=57343?o=(o-55296)*1024+a-56320+65536:i+=this.wcwidth(a)}let h=this.charProperties(o,s),l=Qt.extractWidth(h);Qt.extractShouldJoin(h)&&(l-=Qt.extractWidth(s)),i+=l,s=h}return i}charProperties(t,i){return this._activeProvider.charProperties(t,i)}},ll=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}};function Mr(e){let t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1)?.get(e.cols-1),i=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);i&&t&&(i.isWrapped=t[3]!==0&&t[3]!==32)}var mt=2147483647,cl=256,$n=class vs{constructor(t=32,i=32){if(this.maxLength=t,this.maxSubParamsLength=i,i>cl)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(t),this.length=0,this._subParams=new Int32Array(i),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(t),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let i=new vs;if(!t.length)return i;for(let s=Array.isArray(t[0])?1:0;s>8,r=this._subParamsIdx[i]&255;r-s>0&&t.push(Array.prototype.slice.call(this._subParams,s,r))}return t}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(t){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=t>mt?mt:t}addSubParam(t){if(this._digitIsSub=!0,!!this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=t>mt?mt:t,this._subParamsIdx[this.length-1]++}}hasSubParams(t){return(this._subParamsIdx[t]&255)-(this._subParamsIdx[t]>>8)>0}getSubParams(t){let i=this._subParamsIdx[t]>>8,s=this._subParamsIdx[t]&255;return s-i>0?this._subParams.subarray(i,s):null}getSubParamsAll(){let t={};for(let i=0;i>8,r=this._subParamsIdx[i]&255;r-s>0&&(t[i]=this._subParams.slice(s,r))}return t}addDigit(t){let i;if(this._rejectDigits||!(i=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let s=this._digitIsSub?this._subParams:this.params,r=s[i-1];s[i-1]=~r?Math.min(r*10+t,mt):t}},St=[],dl=class{constructor(){this._state=0,this._active=St,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=St}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=St,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||St,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",ht(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){if(this._state!==3){if(this._state===1)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=St,this._id=-1,this._state=0}}},Un=class qn{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=ht(t,i,s),this._data.length>qn._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};Un._payloadLimit=1e7;var ge=Un,wt=[],ul=class{constructor(){this._handlers=Object.create(null),this._active=wt,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=wt}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=wt,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||wt,!this._active.length)this._handlerFb(this._ident,"HOOK",t);else for(let i=this._active.length-1;i>=0;i--)this._active[i].hook(t)}put(e,t,i){if(!this._active.length)this._handlerFb(this._ident,"PUT",ht(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}unhook(e,t=!0){if(!this._active.length)this._handlerFb(this._ident,"UNHOOK",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].unhook(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=wt,this._ident=0}},Et=new $n;Et.addParam(0);var Vn=class Yn{constructor(t){this._handler=t,this._data="",this._params=Et,this._hitLimit=!1}hook(t){this._params=t.length>1||t.params[0]?t.clone():Et,this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=ht(t,i,s),this._data.length>Yn._payloadLimit&&(this._data="",this._hitLimit=!0))}unhook(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data,this._params),i instanceof Promise))return i.then(s=>(this._params=Et,this._data="",this._hitLimit=!1,s));return this._params=Et,this._data="",this._hitLimit=!1,i}};Vn._payloadLimit=1e7;var Lr=Vn,nt=[],_l=class{constructor(){this._state=0,this._active=nt,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=nt}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=nt,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||nt,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",ht(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){this._state!==3&&(this._state===1&&t0&&this._put(e,t,i))}end(e,t=!0){if(this._state!==0){if(this._state!==3){if(this._state===1){this._active=nt,this._id=-1,this._state=0;return}if(!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}}this._active=nt,this._id=-1,this._state=0}}},jn=class Xn{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=ht(t,i,s),this._data.length>Xn._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};jn._payloadLimit=1e7;var fl=jn,pl=class{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rh),i=(o,h)=>t.slice(o,h),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));let n=i(0,15);e.setDefault(1,0),e.addMany(s,0,2,0);for(let o of n)e.addMany([24,26,153,154],o,3,0),e.addMany(i(128,144),o,3,0),e.addMany(i(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158],o,0,7),e.add(159,o,15,14),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,15,14),e.addMany(s,14,16,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany([27,156,24,26],14,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(28,32),9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(i(28,32),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(28,32),10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(28,32),12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(ue,0,2,0),e.add(ue,8,5,8),e.add(ue,6,0,6),e.add(ue,11,0,11),e.add(ue,13,13,13),e.add(ue,14,16,14),e})(),vl=class extends I{constructor(e=gl){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new $n,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(t,i,s)=>{},this._executeHandlerFb=t=>{},this._csiHandlerFb=(t,i)=>{},this._escHandlerFb=t=>{},this._errorHandlerFb=t=>t,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(q(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new dl),this._dcsParser=this._register(new ul),this._apcParser=this._register(new _l),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let r=0;rn||n>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=n}}if(e.final.length!==1)throw new Error("final must be a single byte");let s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){var r;let i=this._identifier(e,[48,126]);(r=this._escHandlers)[i]??(r[i]=[]);let s=this._escHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){var r;let i=this._identifier(e);(r=this._csiHandlers)[i]??(r[i]=[]);let s=this._csiHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return this._apcParser.registerHandler(e,t)}clearApcHandler(e){this._apcParser.clearHandler(e)}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s=0,r=0,n=0,o;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(i===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");let h=this._parseStack.handlers,l=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](this._params),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 4:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&255}for(let h=n;h>8){case 2:for(let u=h+1;;++u){if(u>=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=0&&(o=l[a](this._params),o!==!0);a--)if(o instanceof Promise)return this._preserveStack(3,l,a,r,h),o;a<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}while(++h47&&s<60);h--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:let d=this._escHandlers[this._collect<<8|s],c=d?d.length-1:-1;for(;c>=0&&(o=d[c](),o!==!0);c--)if(o instanceof Promise)return this._preserveStack(4,d,c,r,h),o;c<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let u=h+1;;++u)if(u>=t||(s=e[u])===24||s===26||s===27||s>127&&s=t||(s=e[u])<32||s>127&&s=t||(s=e[u])===24||s===26||s===27||s===156||s>127&&s>4:n>>8}return s}}function Li(e,t){let i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}function wl(e,t=16){let[i,s,r]=e;return`rgb:${Li(i,t)}/${Li(s,t)}/${Li(r,t)}`}var bl="6.1.0-beta.141",yl={"(":0,")":1,"*":2,"+":3,"-":1,".":2},Ve=131072,Tr=10;function Ar(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var Or=5e3,Ir=0,Cl=class extends I{constructor(e,t,i,s,r,n,o,h,l=new vl){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=n,this._coreMouseService=o,this._unicodeService=h,this._parser=l,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new ho,this._utf8Decoder=new lo,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone(),this._onRequestBell=this._register(new b),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new b),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new b),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new b),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new b),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new b),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new b),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new b),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new b),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new b),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new ms(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(a=>this._activeBuffer=a.activeBuffer)),this._parser.setCsiHandlerFallback((a,d)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(a),params:d.toArray()})}),this._parser.setEscHandlerFallback(a=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(a)})}),this._parser.setExecuteHandlerFallback(a=>{this._logService.debug("Unknown EXECUTE code: ",{code:a})}),this._parser.setOscHandlerFallback((a,d,c)=>{this._logService.debug("Unknown OSC code: ",{identifier:a,action:d,data:c})}),this._parser.setDcsHandlerFallback((a,d,c)=>{d==="HOOK"&&(c=c.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(a),action:d,payload:c})}),this._parser.setPrintHandler((a,d,c)=>this.print(a,d,c)),this._parser.registerCsiHandler({final:"@"},a=>this.insertChars(a)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},a=>this.scrollLeft(a)),this._parser.registerCsiHandler({final:"A"},a=>this.cursorUp(a)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},a=>this.scrollRight(a)),this._parser.registerCsiHandler({final:"B"},a=>this.cursorDown(a)),this._parser.registerCsiHandler({final:"C"},a=>this.cursorForward(a)),this._parser.registerCsiHandler({final:"D"},a=>this.cursorBackward(a)),this._parser.registerCsiHandler({final:"E"},a=>this.cursorNextLine(a)),this._parser.registerCsiHandler({final:"F"},a=>this.cursorPrecedingLine(a)),this._parser.registerCsiHandler({final:"G"},a=>this.cursorCharAbsolute(a)),this._parser.registerCsiHandler({final:"H"},a=>this.cursorPosition(a)),this._parser.registerCsiHandler({final:"I"},a=>this.cursorForwardTab(a)),this._parser.registerCsiHandler({final:"J"},a=>this.eraseInDisplay(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},a=>this.eraseInDisplay(a,!0)),this._parser.registerCsiHandler({final:"K"},a=>this.eraseInLine(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},a=>this.eraseInLine(a,!0)),this._parser.registerCsiHandler({final:"L"},a=>this.insertLines(a)),this._parser.registerCsiHandler({final:"M"},a=>this.deleteLines(a)),this._parser.registerCsiHandler({final:"P"},a=>this.deleteChars(a)),this._parser.registerCsiHandler({final:"S"},a=>this.scrollUp(a)),this._parser.registerCsiHandler({final:"T"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"X"},a=>this.eraseChars(a)),this._parser.registerCsiHandler({final:"Z"},a=>this.cursorBackwardTab(a)),this._parser.registerCsiHandler({final:"^"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"`"},a=>this.charPosAbsolute(a)),this._parser.registerCsiHandler({final:"a"},a=>this.hPositionRelative(a)),this._parser.registerCsiHandler({final:"b"},a=>this.repeatPrecedingCharacter(a)),this._parser.registerCsiHandler({final:"c"},a=>this.sendDeviceAttributesPrimary(a)),this._parser.registerCsiHandler({prefix:">",final:"c"},a=>this.sendDeviceAttributesSecondary(a)),this._parser.registerCsiHandler({final:"d"},a=>this.linePosAbsolute(a)),this._parser.registerCsiHandler({final:"e"},a=>this.vPositionRelative(a)),this._parser.registerCsiHandler({final:"f"},a=>this.hVPosition(a)),this._parser.registerCsiHandler({final:"g"},a=>this.tabClear(a)),this._parser.registerCsiHandler({final:"h"},a=>this.setMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"h"},a=>this.setModePrivate(a)),this._parser.registerCsiHandler({final:"l"},a=>this.resetMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"l"},a=>this.resetModePrivate(a)),this._parser.registerCsiHandler({final:"m"},a=>this.charAttributes(a)),this._parser.registerCsiHandler({final:"n"},a=>this.deviceStatus(a)),this._parser.registerCsiHandler({prefix:"?",final:"n"},a=>this.deviceStatusPrivate(a)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},a=>this.softReset(a)),this._parser.registerCsiHandler({prefix:">",final:"q"},a=>this.sendXtVersion(a)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},a=>this.setCursorStyle(a)),this._parser.registerCsiHandler({final:"r"},a=>this.setScrollRegion(a)),this._parser.registerCsiHandler({final:"s"},a=>this.saveCursor(a)),this._parser.registerCsiHandler({final:"t"},a=>this.windowOptions(a)),this._parser.registerCsiHandler({final:"u"},a=>this.restoreCursor(a)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},a=>this.insertColumns(a)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},a=>this.deleteColumns(a)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},a=>this.selectProtected(a)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},a=>this.requestMode(a,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},a=>this.requestMode(a,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},a=>this.kittyKeyboardSet(a)),this._parser.registerCsiHandler({prefix:"?",final:"u"},a=>this.kittyKeyboardQuery(a)),this._parser.registerCsiHandler({prefix:">",final:"u"},a=>this.kittyKeyboardPush(a)),this._parser.registerCsiHandler({prefix:"<",final:"u"},a=>this.kittyKeyboardPop(a)),this._parser.setExecuteHandler("\x07",()=>this.bell()),this._parser.setExecuteHandler(` +`,()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler(" ",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("\x84",()=>this.index()),this._parser.setExecuteHandler("\x85",()=>this.nextLine()),this._parser.setExecuteHandler("\x88",()=>this.tabSet()),this._parser.registerOscHandler(0,new ge(a=>(this.setTitle(a),this.setIconName(a),!0))),this._parser.registerOscHandler(1,new ge(a=>this.setIconName(a))),this._parser.registerOscHandler(2,new ge(a=>this.setTitle(a))),this._parser.registerOscHandler(4,new ge(a=>this.setOrReportIndexedColor(a))),this._parser.registerOscHandler(8,new ge(a=>this.setHyperlink(a))),this._parser.registerOscHandler(10,new ge(a=>this.setOrReportFgColor(a))),this._parser.registerOscHandler(11,new ge(a=>this.setOrReportBgColor(a))),this._parser.registerOscHandler(12,new ge(a=>this.setOrReportCursorColor(a))),this._parser.registerOscHandler(104,new ge(a=>this.restoreIndexedColor(a))),this._parser.registerOscHandler(110,new ge(a=>this.restoreFgColor(a))),this._parser.registerOscHandler(111,new ge(a=>this.restoreBgColor(a))),this._parser.registerOscHandler(112,new ge(a=>this.restoreCursorColor(a))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(let a in X)this._parser.registerEscHandler({intermediates:"(",final:a},()=>this.selectCharset("("+a)),this._parser.registerEscHandler({intermediates:")",final:a},()=>this.selectCharset(")"+a)),this._parser.registerEscHandler({intermediates:"*",final:a},()=>this.selectCharset("*"+a)),this._parser.registerEscHandler({intermediates:"+",final:a},()=>this.selectCharset("+"+a)),this._parser.registerEscHandler({intermediates:"-",final:a},()=>this.selectCharset("-"+a)),this._parser.registerEscHandler({intermediates:".",final:a},()=>this.selectCharset("."+a)),this._parser.registerEscHandler({intermediates:"/",final:a},()=>this.selectCharset("/"+a));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(a=>(this._logService.error("Parsing error: ",a),a)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new Lr((a,d)=>this.requestStatusString(a,d)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=3){let t,i=new Promise((s,r)=>{t=setTimeout(()=>r("#SLOW_TIMEOUT"),Or)});Promise.race([e,i]).then(()=>{t!==void 0&&clearTimeout(t)},s=>{if(t!==void 0&&clearTimeout(t),s!=="#SLOW_TIMEOUT")throw s;console.warn(`async parser handler taking longer than ${Or} ms`)})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,n=0,o=this._parseStack.paused;if(o){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>Ve&&(n=this._parseStack.position+Ve)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e=="string"?` "${e}"`:` "${Array.prototype.map.call(e,a=>String.fromCharCode(a)).join("")}"`}`),this._logService.logLevel===0&&this._logService.trace("parsing data (codes)",typeof e=="string"?e.split("").map(a=>a.charCodeAt(0)):e),this._parseBuffer.lengthVe)for(let a=n;a0&&c.getWidth(this._activeBuffer.x-1)===2&&c.setCellFromCodepoint(this._activeBuffer.x-1,0,1,d);let u=this._parser.precedingJoinState;for(let f=t;fh){if(l){let E=c,A=this._activeBuffer.x-y;if(this._activeBuffer.x=y,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),c=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!c)return;for(y>0&&c instanceof kt&&c.copyCellsFrom(E,A,0,y,!1);A=0;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d);continue}if(a&&(c.insertCells(this._activeBuffer.x,r-y,this._activeBuffer.getNullCell(d)),c.getWidth(h-1)===2&&c.setCellFromCodepoint(h-1,0,1,d)),c.setCellFromCodepoint(this._activeBuffer.x++,s,r,d),r>0)for(;--r;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d)}this._parser.precedingJoinState=u,this._activeBuffer.x0&&c.getWidth(this._activeBuffer.x)===0&&!c.hasContent(this._activeBuffer.x)&&c.setCellFromCodepoint(this._activeBuffer.x,0,1,d),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final==="t"&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,i=>Ar(i.params[0],this._optionsService.rawOptions.windowOptions)?t(i):!0):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new Lr(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new ge(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new fl(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){let n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n&&(n.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(n.isWrapped=!1))}_resetBufferLine(e,t=!1){let i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let i;switch(e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);i=this._bufferService.cols){let r=this._activeBuffer.lines.get(i+1);r&&(r.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+i)?.getTrimmedLength(););for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let s=this._activeBuffer.lines.length-this._bufferService.rows;s>0&&(this._activeBuffer.lines.trimStart(s),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-s,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-s,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let l=h;for(let a=1;a0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("\x1B[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("\x1B[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("\x1B[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("\x1B[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("\x1B[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`\x1BP>|xterm.js(${bl})\x1B\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(p[p.NOT_RECOGNIZED=0]="NOT_RECOGNIZED",p[p.SET=1]="SET",p[p.RESET=2]="RESET",p[p.PERMANENTLY_SET=3]="PERMANENTLY_SET",p[p.PERMANENTLY_RESET=4]="PERMANENTLY_RESET"))(void 0||(i={}));let s=this._coreService.decPrivateModes,{activeProtocol:r,activeEncoding:n}=this._coreMouseService,o=this._coreService,{buffers:h,cols:l}=this._bufferService,{active:a,alt:d}=h,c=this._optionsService.rawOptions,u=(p,y)=>(o.triggerDataEvent(`\x1B[${t?"":"?"}${p};${y}$y`),!0),f=p=>p?1:2,_=e.params[0];return t?_===2?u(_,4):_===4?u(_,f(o.modes.insertMode)):_===12?u(_,3):_===20?u(_,f(c.convertEol)):u(_,0):_===1?u(_,f(s.applicationCursorKeys)):_===3?u(_,c.windowOptions.setWinLines?l===80?2:l===132?1:0:0):_===6?u(_,f(s.origin)):_===7?u(_,f(s.wraparound)):_===8?u(_,3):_===9?u(_,f(r==="X10")):_===12?u(_,f(c.cursorBlink)):_===25?u(_,f(!o.isCursorHidden)):_===45?u(_,f(s.reverseWraparound)):_===66?u(_,f(s.applicationKeypad)):_===67?u(_,4):_===1e3?u(_,f(r==="VT200")):_===1002?u(_,f(r==="DRAG")):_===1003?u(_,f(r==="ANY")):_===1004?u(_,f(s.sendFocus)):_===1005?u(_,4):_===1006?u(_,f(n==="SGR")):_===1015?u(_,4):_===1016?u(_,f(n==="SGR_PIXELS")):_===1048?u(_,1):_===47||_===1047||_===1049?u(_,f(a===d)):_===2004?u(_,f(s.bracketedPasteMode)):_===2026?u(_,f(s.synchronizedOutput)):_===9001&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?u(_,f(s.win32InputMode)):u(_,0)}_updateAttrColor(e,t,i,s,r){return t===2?(e|=50331648,e&=-16777216,e|=Tt.fromColorRGB([i,s,r])):t===5&&(e&=-50331904,e|=33554432|i&255),e}_extractColor(e,t,i){let s=[0,0,-1,0,0,0],r=0,n=0;do{if(s[n+r]=e.params[t+n],e.hasSubParams(t+n)){let o=e.getSubParams(t+n),h=0;do s[1]===5&&(r=1),s[n+h+1+r]=o[h];while(++h=2||s[1]===2&&n+r>=5)break;s[1]&&(r=1)}while(++n+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=Q.fg,e.bg=Q.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,i,s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-50331904,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-50331904,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-50331904,s.fg|=16777216|i-90|8):i>=100&&i<=107?(s.bg&=-50331904,s.bg|=16777216|i-100|8):i===0?this._processSGR0(s):i===1?s.fg|=134217728:i===3?s.bg|=67108864:i===4?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):i===5?s.fg|=536870912:i===7?s.fg|=67108864:i===8?s.fg|=1073741824:i===9?s.fg|=2147483648:i===2?s.bg|=134217728:i===21?this._processUnderline(2,s):i===22?(s.fg&=-134217729,s.bg&=-134217729):i===23?s.bg&=-67108865:i===24?(s.fg&=-268435457,this._processUnderline(0,s)):i===25?s.fg&=-536870913:i===27?s.fg&=-67108865:i===28?s.fg&=-1073741825:i===29?s.fg&=2147483647:i===39?(s.fg&=-67108864,s.fg|=Q.fg&16777215):i===49?(s.bg&=-67108864,s.bg|=Q.bg&16777215):i===38||i===48||i===58?r+=this._extractColor(e,r,s):i===53?s.bg|=1073741824:i===55?s.bg&=-1073741825:i===221&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.fg&=-134217729:i===222&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.bg&=-134217729:i===59?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("\x1B[0n");break;case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[${t};${i}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[?${t};${i}R`);break;case 15:break;case 25:break;case 26:break;case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??!0)&&this._onRequestColorSchemeQuery.fire();break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=Q.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar";break}let i=t%2===1;this._coreService.decPrivateModes.cursorBlink=i}return!0}setScrollRegion(e){let t=e.params[0]||1,i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||i===0)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!Ar(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`\x1B[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>Tr&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>Tr&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let t=0;t1;){let s=i.shift(),r=i.shift();if(/^\d+$/.exec(s)){let n=parseInt(s);if(Nr(n))if(r==="?")t.push({type:0,index:n});else{let o=Pr(r);o&&t.push({type:1,index:n,color:o})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(";");if(t===-1)return!0;let i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):i.trim()?!1:this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let i=e.split(":"),s,r=i.findIndex(n=>n.startsWith("id="));return r!==-1&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let i=e.split(";");for(let s=0;s=this._specialColors.length);++s,++t)if(i[s]==="?")this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let r=Pr(i[s]);r&&this._onColor.fire([{type:1,index:this._specialColors[t],color:r}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],i=e.split(";");for(let s=0;s=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new ke;e.content=1<<22|69,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`\x1B${o}\x1B\\`),!0),s=this._bufferService.buffer,r=this._optionsService.rawOptions;return i(e==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:e==='"p'?'P1$r61;1"p':e==="r"?`P1$r${s.scrollTop+1};${s.scrollBottom+1}r`:e==="m"?"P1$r0m":e===" q"?`P1$r${{block:2,underline:4,bar:6}[r.cursorStyle]-(r.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t;break}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`\x1B[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let r=0;r0;r++)i.flags=s.pop();return s.length===0&&t>0&&(i.flags=0),!0}},ms=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(Ir=e,e=t,t=Ir),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};ms=K([w(0,ce)],ms);function Nr(e){return 0<=e&&e<256}var kl=5e7,Fr=12,xl=50,El=class extends I{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._isSyncWriting)return;this._isSyncWriting=!0;let e;for(;e=this._writeBuffer.shift();){this._action(e);let t=this._callbacks.shift();t&&t()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1}writeSync(e,t){if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let i;for(;i=this._writeBuffer.shift();){this._action(i);let s=this._callbacks.shift();s&&s()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>kl)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){let i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let s=this._writeBuffer[this._bufferOffset],r=this._action(s,t);if(r){let o=h=>performance.now()-i>=Fr?setTimeout(()=>this._innerWrite(0,h)):this._innerWrite(i,h);r.catch(h=>(queueMicrotask(()=>{throw h}),Promise.resolve(!1))).then(o);return}let n=this._callbacks[this._bufferOffset];if(n&&n(),this._bufferOffset++,this._pendingData-=s.length,performance.now()-i>=Fr)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>xl&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}},Ss=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){let t=this._bufferService.buffer;if(e.id===void 0){let h=t.addMarker(t.ybase+t.y),l={data:e,id:this._nextId++,lines:[h]};return h.onDispose(()=>this._removeMarkerFromLink(l,h)),this._dataByLinkId.set(l.id,l),l.id}let i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;let n=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[n]};return n.onDispose(()=>this._removeMarkerFromLink(o,n)),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){let i=this._dataByLinkId.get(e);if(i&&i.lines.every(s=>s.line!==t)){let s=this._bufferService.buffer.addMarker(t);i.lines.push(s),s.onDispose(()=>this._removeMarkerFromLink(i,s))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){let i=e.lines.indexOf(t);i!==-1&&(e.lines.splice(i,1),e.lines.length===0&&(e.data.id!==void 0&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};Ss=K([w(0,ce)],Ss);var Hr=!1,Bl=class extends I{constructor(e){super(),this._windowsWrappingHeuristics=this._register(new at),this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onData=this._register(new b),this.onData=this._onData.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new b),this._instantiationService=new jh,this.optionsService=this._register(new rl(e)),this._instantiationService.setService(he,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(fs)),this._instantiationService.setService(ce,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(_s)),this._instantiationService.setService(Zr,this._logService),this.coreService=this._register(this._instantiationService.createInstance(ps)),this._instantiationService.setService(Xe,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(gs)),this._instantiationService.setService(Jr,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(et)),this._instantiationService.setService(fo,this.unicodeService),this._charsetService=this._instantiationService.createInstance(ll),this._instantiationService.setService(_o,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(Ss),this._instantiationService.setService(Qr,this._oscLinkService),this._inputHandler=this._register(new Cl(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(_e.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(_e.forward(this._bufferService.onResize,this._onResize)),this._register(_e.forward(this.coreService.onData,this._onData)),this._register(_e.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new El((t,i)=>this._inputHandler.parse(t,i))),this._register(_e.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new b),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!Hr&&(this._logService.warn("writeSync is unreliable and will be removed soon."),Hr=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,zn),t=Math.max(t,Kn),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.buildNumber!==void 0&&t.buildNumber!==void 0&&(e=t.backend==="conpty"&&t.buildNumber<21376),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Mr.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>(Mr(this._bufferService),!1))),this._windowsWrappingHeuristics.value=q(()=>{for(let t of e)t.dispose()})}}},U=0,Dl=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new hi,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new hi,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){let e=this._insertedValues.sort((r,n)=>this._getKey(r)-this._getKey(n)),t=0,i=0,s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),this._array.length===0)return!1;let t=this._getKey(e);if(t===void 0||(U=this._search(t),U===-1)||this._getKey(this._array[U])!==t)return!1;do if(this._array[U]===e)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(U),!0;while(++Ur-n),t=0,i=new Array(this._array.length-e.length),s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(U=this._search(e),!(U<0||U>=this._array.length)&&this._getKey(this._array[U])===e))do yield this._array[U];while(++U=this._array.length)&&this._getKey(this._array[U])===e))do t(this._array[U]);while(++U=t;){let s=t+i>>1,r=this._getKey(this._array[s]);if(r>e)i=s-1;else if(r0&&this._getKey(this._array[s-1])===e;)s--;return s}}return t}},Pi=0,Wr=0,Ti=0,zr=0,Rl=class extends I{constructor(){super(),this._decorations=new Dl(e=>e?.marker.line),this._onDecorationRegistered=this._register(new b),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new b),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register(q(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new Ml(e);if(t){let i=t.marker.onDispose(()=>t.dispose()),s=t.onDispose(()=>{s.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),i.dispose())});this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,i){let s=0,r=0,n=0,o=0;for(let h of this._decorations.values())n=h.marker.line,o=n+(h.options.height??1),!(t=o)&&(s=h.options.x??0,r=s+(h.options.width??1),e>=s&&e=zr)&&(Pi=r.options.x??0,Wr=Pi+(r.options.width??1),e>=Pi&&e=this._debounceThresholdMS)this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){let r=s-this._lastRefreshMs,n=this._debounceThresholdMS-r;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},n)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}},Kr=20,$r=!1,li=class extends I{constructor(e,t,i,s){super(),this._terminal=e,this._coreBrowserService=i,this._renderService=s,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="";let r=this._coreBrowserService.mainDocument;this._accessibilityContainer=r.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=r.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let n=0;nthis._handleBoundaryFocus(n,0),this._bottomBoundaryFocusListener=n=>this._handleBoundaryFocus(n,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Pl(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");$r?(this._accessibilityContainer.classList.add("debug"),this._rowContainer.classList.add("debug"),this._debugRootContainer=r.createElement("div"),this._debugRootContainer.classList.add("xterm"),this._debugRootContainer.appendChild(r.createTextNode("------start a11y------")),this._debugRootContainer.appendChild(this._accessibilityContainer),this._debugRootContainer.appendChild(r.createTextNode("------end a11y------")),this._terminal.element.insertAdjacentElement("afterend",this._debugRootContainer)):this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(n=>this._handleResize(n.rows))),this._register(this._terminal.onRender(n=>this._refreshRows(n.start,n.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(n=>this._handleChar(n))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` +`))),this._register(this._terminal.onA11yTab(n=>this._handleTab(n))),this._register(this._terminal.onKey(n=>this._handleKey(n.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(B(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(q(()=>{$r?this._debugRootContainer.remove():this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` +`&&(this._liveRegionLineCount++,this._liveRegionLineCount===Kr+1&&(this._liveRegion.textContent=ei.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){let n=i.lines.get(i.ydisp+r),o=[],h=n?.translateToString(!0,void 0,void 0,o)||"",l=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(h.length===0?(a.textContent="\xA0",this._rowColumns.set(a,[0,1])):(a.textContent=h,this._rowColumns.set(a,o)),a.setAttribute("aria-posinset",l),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent===ei.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){let i=e.target,s=this._rowElements[t===0?1:this._rowElements.length-2],r=i.getAttribute("aria-posinset"),n=t===0?"1":`${this._terminal.buffer.lines.length}`;if(r===n||e.relatedTarget!==s)return;let o,h;if(t===0?(o=i,h=this._rowElements.pop(),this._rowContainer.removeChild(h)):(o=this._rowElements.shift(),h=i,this._rowContainer.removeChild(o)),o.removeEventListener("focus",this._topBoundaryFocusListener),h.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){let l=this._createAccessibilityTreeNode();this._rowElements.unshift(l),this._rowContainer.insertAdjacentElement("afterbegin",l)}else{let l=this._createAccessibilityTreeNode();this._rowElements.push(l),this._rowContainer.appendChild(l)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error("anchorNode and/or focusNode are null");return}let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;let r=({node:h,offset:l})=>{let a=h instanceof Text?h.parentNode:h,d=parseInt(a?.getAttribute("aria-posinset"),10)-1;if(isNaN(d))return console.warn("row is invalid. Race condition?"),null;let c=this._rowColumns.get(a);if(!c)return console.warn("columns is null. Race condition?"),null;let u=l=this._terminal.cols&&(++d,u=0),{row:d,column:u}},n=r(t),o=r(i);if(!(!n||!o)){if(n.row>o.row||n.row===o.row&&n.column>=o.column)throw new Error("invalid range");this._terminal.select(n.column,n.row,(o.row-n.row)*this._terminal.cols-n.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;te;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Fi(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(B(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(B(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(B(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(B(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;let i=e.composedPath();for(let s=0;s{s?.forEach(r=>{r.link.dispose&&r.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(let[s,r]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(s)&&(i=this._checkLinkProviderResult(s,e,i)):r.provideLinks(e.y,n=>{if(this._isMouseOut)return;let o=n?.map(h=>({link:h}));this._activeProviderReplies?.set(s,o),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let i=new Set;for(let s=0;se?this._bufferService.cols:o.link.range.end.x;for(let a=h;a<=l;a++){if(i.has(a)){r.splice(n--,1);break}i.add(a)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;let s=this._activeProviderReplies.get(e),r=!1;for(let n=0;nthis._linkAtPosition(o.link,t));n&&(i=!0,this._handleNewLink(n))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let n=0;nthis._linkAtPosition(h.link,t));if(o){i=!0,this._handleNewLink(o);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink&&Tl(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Fi(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0?!0:e.link.decorations.underline,pointerCursor:e.link.decorations===void 0?!0:e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:i=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==i&&(this._currentLink.state.decorations.pointerCursor=i,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",i))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:i=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==i&&(this._currentLink.state.decorations.underline=i,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,i))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(i=>{if(!this._currentLink)return;let s=i.start===0?0:i.start+1+this._bufferService.buffer.ydisp,r=this._bufferService.buffer.ydisp+1+i.end;if(this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=r&&(this._clearCurrentLink(s,r),this._lastMouseEvent)){let n=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);n&&this._askForLink(n,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){let i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){let i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t,i){let s=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(s)return{x:s[0],y:s[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};ws=K([w(1,ys),w(2,Fe),w(3,ce),w(4,tn)],ws);function Tl(e,t){return e.text===t.text&&e.range.start.x===t.range.start.x&&e.range.start.y===t.range.start.y&&e.range.end.x===t.range.end.x&&e.range.end.y===t.range.end.y}var Al=class extends Bl{constructor(e={}){super(e),this._linkifier=this._register(new at),this.browser=Mn,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new at),this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new b),this.onKey=this._onKey.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new b),this.onBell=this._onBell.event,this._onFocus=this._register(new b),this._onBlur=this._register(new b),this._onA11yCharEmitter=this._register(new b),this._onA11yTabEmitter=this._register(new b),this._onWillOpen=this._register(new b),this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._setup(),this._decorationService=this._instantiationService.createInstance(Rl),this._instantiationService.setService(At,this._decorationService),this._keyboardService=this._instantiationService.createInstance(us),this._instantiationService.setService(vo,this._keyboardService),this._linkProviderService=this._instantiationService.createInstance(bh),this._instantiationService.setService(tn,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(Ni)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(t=>this.refresh(t?.start??0,t?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(t=>this._reportWindowsOptions(t))),this._register(this._inputHandler.onColor(t=>this._handleColorEvent(t))),this._register(_e.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(_e.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(_e.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(_e.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(t=>this._afterResize(t.cols,t.rows))),this._register(q(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}get dimensions(){if(!this._renderService)return;let e=this._renderService.dimensions;return{css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}}_handleColorEvent(e){if(this._themeService)for(let t of e){let i,s="";switch(t.index){case 256:i="foreground",s="10";break;case 257:i="background",s="11";break;case 258:i="cursor",s="12";break;default:i="ansi",s="4;"+t.index}switch(t.type){case 0:let r=N.toColorRGB(i==="ansi"?this._themeService.colors.ansi[t.index]:this._themeService.colors[i]);this.coreService.triggerDataEvent(`\x1B]${s};${wl(r)}\x1B\\`);break;case 1:if(i==="ansi")this._themeService.modifyColors(n=>n.ansi[t.index]=V.toColor(...t.color));else{let n=i;this._themeService.modifyColors(o=>o[n]=V.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_reportColorScheme(){if(!this._themeService)return;let e=oe.relativeLuminance(this._themeService.colors.background.rgba>>8),t=oe.relativeLuminance(this._themeService.colors.foreground.rgba>>8),i=e{this.hasSelection()&&oo(t,this._selectionService)}));let e=t=>ao(t,this.textarea,this.coreService,this.optionsService);this._register(B(this.textarea,"paste",e)),this._register(B(this.element,"paste",e)),Ln?this._register(B(this.element,"mousedown",t=>{t.button===2&&Ws(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(B(this.element,"contextmenu",t=>{Ws(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),As&&this._register(B(this.element,"auxclick",t=>{t.button===1&&Yr(t,this.textarea,this.screenElement)}))}_bindKeys(){this._register(B(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(B(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(B(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(B(this.textarea,"compositionstart",()=>this._compositionHelper.compositionstart())),this._register(B(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(B(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(B(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",r=>this.element.classList.toggle("allow-transparency",r))),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(B(this.screenElement,"mousemove",r=>this.updateCursorStyle(r))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",Oi.get()),An||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(Sh,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<"u"?window.document:null)),this._instantiationService.setService(Ne,this._coreBrowserService),this._register(B(this.textarea,"focus",r=>this._handleTextAreaFocus(r))),this._register(B(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(as,this._document,this._helperContainer),this._instantiationService.setService(ci,this._charSizeService),this._themeService=this._instantiationService.createInstance(ds),this._instantiationService.setService(lt,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(ai),this._instantiationService.setService(en,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(ls,this.rows,this.screenElement)),this._instantiationService.setService(Fe,this._renderService),this._register(this._renderService.onRenderedViewportChange(r=>this._onRender.fire(r))),this._register(this._renderService.onDimensionsChange(r=>this._onDimensionsChange.fire({css:{canvas:{...r.css.canvas},cell:{...r.css.cell}},device:{canvas:{...r.device.canvas},cell:{...r.device.cell},char:{...r.device.char}}}))),this.onResize(r=>this._renderService.resize(r.cols,r.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(rs,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(hs),this._instantiationService.setService(ys,this._mouseService);let s=this._linkifier.value=this._register(this._instantiationService.createInstance(ws,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(is,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(r=>{super.scrollLines(r,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(cs,this.element,this.screenElement,s)),this._instantiationService.setService(go,this._selectionService),this._register(this._selectionService.onRequestScrollLines(r=>this.scrollLines(r.amount,r.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(r=>this._renderService.handleSelectionChanged(r.start,r.end,r.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(r=>{this.textarea.value=r,this.textarea.focus(),this.textarea.select()})),this._register(_e.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(ss,this.screenElement)),this._register(B(this.element,"mousedown",r=>this._selectionService.handleMouseDown(r))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(li,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",r=>this._handleScreenReaderModeOptionChange(r))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(oi,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRuler",r=>{!this._overviewRulerRenderer&&r&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(oi,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(os,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,t=this.element;function i(n){let o=e._mouseService?.getMouseReportCoords(n,e.screenElement);if(!o)return!1;let h,l;switch(n.overrideType||n.type){case"mousemove":l=32,n.buttons===void 0?(h=3,n.button!==void 0&&(h=n.button<3?n.button:3)):h=n.buttons&1?0:n.buttons&4?1:n.buttons&2?2:3;break;case"mouseup":l=0,h=n.button<3?n.button:3;break;case"mousedown":l=1,h=n.button<3?n.button:3;break;case"wheel":if(e._customWheelEventHandler&&e._customWheelEventHandler(n)===!1)return!1;let a=n.deltaY;if(a===0||e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;l=a<0?0:1,h=4;break;default:return!1}return l===void 0||h===void 0||h>4?!1:e.coreMouseService.triggerMouseEvent({col:o.col,row:o.row,x:o.x,y:o.y,button:h,action:l,ctrl:n.ctrlKey,alt:n.altKey,shift:n.shiftKey})}let s={mouseup:null,wheel:null,mousedrag:null,mousemove:null},r={mouseup:n=>{i(n),n.buttons||(this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag))},wheel:n=>(i(n),n.preventDefault(),n.stopPropagation(),!1),mousedrag:n=>{n.buttons&&i(n)},mousemove:n=>{n.buttons||i(n)}};this._register(this.coreMouseService.onProtocolChange(n=>{n?(this.optionsService.rawOptions.logLevel==="debug"&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(n)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),n&8?s.mousemove||(t.addEventListener("mousemove",r.mousemove),s.mousemove=r.mousemove):(t.removeEventListener("mousemove",s.mousemove),s.mousemove=null),n&16?s.wheel||(t.addEventListener("wheel",r.wheel,{passive:!1}),s.wheel=r.wheel):(t.removeEventListener("wheel",s.wheel),s.wheel=null),n&2?s.mouseup??(s.mouseup=r.mouseup):(this._document.removeEventListener("mouseup",s.mouseup),s.mouseup=null),n&4?s.mousedrag??(s.mousedrag=r.mousedrag):(this._document.removeEventListener("mousemove",s.mousedrag),s.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(q(()=>{s.mouseup&&this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag)})),this._register(B(t,"mousedown",n=>{n.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(n))&&(i(n),s.mouseup&&this._document.addEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.addEventListener("mousemove",s.mousedrag))})),this._register(B(t,"wheel",n=>{if(!s.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(n)===!1)return!1;if(!this.buffer.hasScrollback){if(n.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return n.preventDefault(),n.stopPropagation(),!1;let o="\x1B"+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(n.deltaY<0?"A":"B");return this.coreService.triggerDataEvent(o,!0),n.preventDefault(),n.stopPropagation(),!1}}},{passive:!1}))}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){Vr(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),i.type===3||i.type===2){let r=this.rows-1;return this.scrollLines(i.type===2?-r:r),e.preventDefault(),e.stopPropagation(),!1}if(i.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)||!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;(i.key===""||i.key==="\r")&&(this.textarea.value="");let s=this._keyboardService.useWin32InputMode&&Ai(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){let i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return t.type==="keypress"?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return;Ai(e)||this.focus();let t=this._keyboardService.evaluateKeyUp(e);if(t?.key){let i=this._keyboardService.useWin32InputMode&&Ai(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType==="insertText"&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(!(this.buffer.ybase===0&&this.buffer.y===0)){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){let i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new ke)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}},Ur=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){let t=this._buffer.lines.get(e);if(t)return new Il(t)}getNullCell(){return new ke}},Nl=class extends I{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new b),this.onBufferChange=this._onBufferChange.event,this._normal=new Ur(this._core.buffers.normal,"normal"),this._alternate=new Ur(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}},Fl=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,i=>t(i.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(i,s)=>t(i,s.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}},Hl=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}},Wl=["cols","rows"],De=0,Gn=class extends I{constructor(e){super(),this._core=this._register(new Al(e)),this._addonManager=this._register(new Ol),this._publicOptions={...this._core.options};let t=s=>this._core.options[s],i=(s,r)=>{this._checkReadonlyOptions(s),this._core.options[s]=r};for(let s in this._core.options){let r={get:t.bind(this,s),set:i.bind(this,s)};Object.defineProperty(this._publicOptions,s,r)}}_checkReadonlyOptions(e){if(Wl.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get parser(){return this._parser??(this._parser=new Fl(this._core))}get unicode(){return this._checkProposedApi(),new Hl(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??(this._buffer=this._register(new Nl(this._core)))}get markers(){return this._core.markers}get modes(){let e=this._core.coreService.decPrivateModes,t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any";break}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(let t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write(`\r +`,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return Oi.get()},set promptLabel(e){Oi.set(e)},get tooMuchOutput(){return ei.get()},set tooMuchOutput(e){ei.set(e)}}}_verifyIntegers(...e){for(De of e)if(De===1/0||isNaN(De)||De%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(De of e)if(De&&(De===1/0||isNaN(De)||De%1!==0||De<0))throw new Error("This API only accepts positive integers")}};/** * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved. * @license MIT * @@ -55,4 +55,4 @@ ${d.join(` * Fabrice Bellard's javascript vt100 for jslinux: * http://bellard.org/jslinux/ * Copyright (c) 2011 Fabrice Bellard - */var Fl=2,Hl=1;function Wl(e){return e?.ownerDocument?.defaultView?e.ownerDocument.defaultView:window}function Xn(e){return Wl(e).getComputedStyle(e,null)}var Gn=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows)||(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&this._terminal.resize(e.cols,e.rows)}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let e=this._terminal.dimensions;if(!e||e.css.cell.width===0||e.css.cell.height===0)return;let t=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,i=Xn(this._terminal.element.parentElement),s=parseInt(i.getPropertyValue("height")),r=Math.max(0,parseInt(i.getPropertyValue("width"))),n=Xn(this._terminal.element),o={top:parseInt(n.getPropertyValue("padding-top")),bottom:parseInt(n.getPropertyValue("padding-bottom")),right:parseInt(n.getPropertyValue("padding-right")),left:parseInt(n.getPropertyValue("padding-left"))},h=o.top+o.bottom,l=o.right+o.left,a=s-h,d=r-l-t;return{cols:Math.max(Fl,Math.floor(d/e.css.cell.width)),rows:Math.max(Hl,Math.floor(a/e.css.cell.height))}}};var Fe=new jn({cursorBlink:!0,scrollback:5e3,convertEol:!1,fontSize:14}),Os=new Gn;Fe.loadAddon(Os);Fe.open(document.getElementById("terminal"));var Jn="",ui=-1,As=[],Ts=!1;function Zn(){if(Ts||!As.length)return;let e=As.shift();Ts=!0,e.reset&&Fe.reset(),e.theme&&(Fe.options.theme=e.theme),Fe.write(e.data,()=>{Ts=!1,Zn()})}function ct(e){window.OpenBitFunTerminalHost?window.OpenBitFunTerminalHost.postMessage(JSON.stringify(e)):window.webkit?.messageHandlers?.openbitfunTerminal?.postMessage(e)}Fe.onData(e=>ct({type:"input",data:e}));Fe.onResize(({cols:e,rows:t})=>ct({type:"resize",cols:e,rows:t}));new ResizeObserver(()=>Os.fit()).observe(document.getElementById("terminal"));window.OpenBitFunTerminal={connect(){Os.fit(),ct({type:"ready"}),ct({type:"resize",cols:Fe.cols,rows:Fe.rows})},accept(e){if(!(typeof e.epoch!="string"||!Number.isSafeInteger(e.revision)||typeof e.data!="string")){if(e.epoch!==Jn){if(!e.reset){ct({type:"resync"});return}Jn=e.epoch,ui=-1}if(!(e.revision<=ui)){if(!e.reset&&e.revision!==ui+1){ct({type:"resync"});return}As.push(e),ui=e.revision,Zn()}}}};window.OpenBitFunTerminal.connect();})(); + */var zl=2,Kl=1;function $l(e){return e?.ownerDocument?.defaultView?e.ownerDocument.defaultView:window}function Jn(e){return $l(e).getComputedStyle(e,null)}var Zn=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows)||(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&this._terminal.resize(e.cols,e.rows)}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let e=this._terminal.dimensions;if(!e||e.css.cell.width===0||e.css.cell.height===0)return;let t=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,i=Jn(this._terminal.element.parentElement),s=parseInt(i.getPropertyValue("height")),r=Math.max(0,parseInt(i.getPropertyValue("width"))),n=Jn(this._terminal.element),o={top:parseInt(n.getPropertyValue("padding-top")),bottom:parseInt(n.getPropertyValue("padding-bottom")),right:parseInt(n.getPropertyValue("padding-right")),left:parseInt(n.getPropertyValue("padding-left"))},h=o.top+o.bottom,l=o.right+o.left,a=s-h,d=r-l-t;return{cols:Math.max(zl,Math.floor(d/e.css.cell.width)),rows:Math.max(Kl,Math.floor(a/e.css.cell.height))}}};var Le=new Gn({cursorBlink:!0,scrollback:5e3,convertEol:!1,fontSize:14}),Ns=new Zn;Le.loadAddon(Ns);Le.open(document.getElementById("terminal"));var It=document.getElementById("terminal"),He;It.addEventListener("touchstart",e=>{let t=e.touches[0];He=e.touches.length===1?{id:t.identifier,x:t.clientX,y:t.clientY,started:e.timeStamp}:void 0},{passive:!0});It.addEventListener("touchmove",e=>{let t=Array.from(e.touches).find(i=>i.identifier===He?.id);(!t||!He||Math.hypot(t.clientX-He.x,t.clientY-He.y)>10)&&(He=void 0)},{passive:!0});It.addEventListener("touchcancel",()=>{He=void 0},{passive:!0});It.addEventListener("touchend",e=>{let t=He;He=void 0,t&&e.touches.length===0&&e.timeStamp-t.started<500&&Array.from(e.changedTouches).some(i=>i.identifier===t.id&&Math.hypot(i.clientX-t.x,i.clientY-t.y)<=10)&&Le.focus()},{passive:!0});function eo(e){Le.options.theme=e,It.style.backgroundColor=e.background}var Qn="",fi=-1,Is=[],Os=!1;function to(){if(Os||!Is.length)return;let e=Is.shift();Os=!0,e.reset&&Le.reset(),e.theme&&eo(e.theme),Le.write(e.data,()=>{Os=!1,to()})}function dt(e){window.OpenBitFunTerminalHost?window.OpenBitFunTerminalHost.postMessage(JSON.stringify(e)):window.webkit?.messageHandlers?.openbitfunTerminal?.postMessage(e)}Le.onData(e=>dt({type:"input",data:e}));Le.onResize(({cols:e,rows:t})=>dt({type:"resize",cols:e,rows:t}));new ResizeObserver(()=>Ns.fit()).observe(document.getElementById("terminal"));window.OpenBitFunTerminal={setTheme:eo,connect(){Ns.fit(),dt({type:"ready"}),dt({type:"resize",cols:Le.cols,rows:Le.rows})},accept(e){if(!(typeof e.epoch!="string"||!Number.isSafeInteger(e.revision)||typeof e.data!="string")){if(e.epoch!==Qn){if(!e.reset){dt({type:"resync"});return}Qn=e.epoch,fi=-1}if(!(e.revision<=fi)){if(!e.reset&&e.revision!==fi+1){dt({type:"resync"});return}Is.push(e),fi=e.revision,to()}}}};window.OpenBitFunTerminal.connect();})(); diff --git a/src/apps/mobile/harmonyos/entry/src/test/ConversationPresentationUnit.test.ets b/src/apps/mobile/harmonyos/entry/src/test/ConversationPresentationUnit.test.ets index b0b732ef5f..6b8a2e20f2 100644 --- a/src/apps/mobile/harmonyos/entry/src/test/ConversationPresentationUnit.test.ets +++ b/src/apps/mobile/harmonyos/entry/src/test/ConversationPresentationUnit.test.ets @@ -762,7 +762,8 @@ export default function conversationPresentationUnitTest() { expect(RemoteCompactHomePolicy.shouldShowConnectHome('failed')).assertTrue(); expect(RemoteCompactHomePolicy.shouldShowConnectHome('disconnected')).assertTrue(); expect(RemoteCompactHomePolicy.shouldShowConnectHome('idle')).assertTrue(); - expect(RemoteCompactHomePolicy.headerSubtitle('reconnecting', 'Studio')).assertEqual(''); + expect(RemoteCompactHomePolicy.headerSubtitle('reconnecting', 'Studio')).assertEqual('Studio'); + expect(RemoteCompactHomePolicy.headerSubtitle('idle', 'Studio')).assertEqual(''); expect(RemoteCompactHomePolicy.headerSubtitle('connected', 'Studio')).assertEqual('Studio'); }); }); diff --git a/src/apps/mobile/harmonyos/entry/src/test/DeviceDirectoryUnit.test.ets b/src/apps/mobile/harmonyos/entry/src/test/DeviceDirectoryUnit.test.ets index 828513d00d..9a09c75de8 100644 --- a/src/apps/mobile/harmonyos/entry/src/test/DeviceDirectoryUnit.test.ets +++ b/src/apps/mobile/harmonyos/entry/src/test/DeviceDirectoryUnit.test.ets @@ -1,3 +1,4 @@ +import { WorkspaceCatalog, mergeLegacyWorkspaces } from '../main/ets/services/WorkspaceCatalog'; import { describe, it, expect } from '@ohos/hypium'; import { AssistantEntry, RecentWorkspaceEntry, RemoteSession, SessionListResult } from '../main/ets/model/RemoteModels'; import { AccountDeviceDirectorySource } from '../main/ets/services/AccountDeviceDirectory'; @@ -22,6 +23,11 @@ class FakeAccountDeviceDirectory implements AccountDeviceDirectorySource { failingDevices: string[] = []; failingPaths: string[] = []; + async listWorkspaceCatalog(deviceId: string): Promise { + const recent = await this.listRecentWorkspaces(deviceId); + return { workspaces: mergeLegacyWorkspaces(recent, await this.listAssistants(deviceId)), recentWorkspaces: recent, source: 'recent' }; + } + async listRecentWorkspaces(deviceId: string): Promise { this.listedDeviceIds.push(deviceId); if (this.failingDevices.includes(deviceId)) { diff --git a/src/apps/mobile/harmonyos/entry/src/test/LocalTestFixtures.ets b/src/apps/mobile/harmonyos/entry/src/test/LocalTestFixtures.ets index 6f9b7ef811..a336257500 100644 --- a/src/apps/mobile/harmonyos/entry/src/test/LocalTestFixtures.ets +++ b/src/apps/mobile/harmonyos/entry/src/test/LocalTestFixtures.ets @@ -1,3 +1,4 @@ +import { WorkspaceCatalog, mergeLegacyWorkspaces } from '../main/ets/services/WorkspaceCatalog'; import { DurableSessionStream, DurableSessionCallbacks, DurableSessionEvent, DurableSessionSource, SessionKeyGrant } from '../main/ets/services/DurableSessionStream'; import { SessionRecord } from '../main/ets/services/DurableSessionReducer'; import { SavedRuntimeConnection } from '../main/ets/model/RemoteModels'; @@ -169,6 +170,11 @@ export class FakeRemoteWorkspaceDataSource implements RemoteWorkspaceDataSource /** Every path listed, in the order the listings were started. */ listedPaths: string[] = []; + async listWorkspaceCatalog(): Promise { + const recent = await this.listRecentWorkspaces(); + return { workspaces: mergeLegacyWorkspaces(recent, await this.listAssistants()), recentWorkspaces: recent, source: 'recent' }; + } + async listRecentWorkspaces(): Promise { return this.recent; } diff --git a/src/apps/mobile/harmonyos/tools/tests/account-restore.test.cjs b/src/apps/mobile/harmonyos/tools/tests/account-restore.test.cjs new file mode 100644 index 0000000000..1c929588fe --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/account-restore.test.cjs @@ -0,0 +1,37 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +const official = 'https://remote.openbitfun.com/v/1.0.1'; +const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets/pages/viewmodel/SettingsController.ets'), 'utf8'); +const js = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText; +const exported = {}; +new Function('require', 'exports', js)(name => ({ + '../../services/AccountDeviceLink': { OFFICIAL_RELAY_URL: official }, + '../../services/CloudAccountClient': { DEFAULT_CLOUD_RELAY_URL: official }, + '../../services/RemoteLogger': { RemoteLogger: { warn() {} } }, + '../../i18n/RemoteI18n': { RemoteI18n: { t: key => key } }, +}[name] || {}), exported); + +test('an older relay credential retains its saved profile without claiming current authentication', async () => { + const record = { relayUrl: 'https://remote.openbitfun.com/v/1.0.0', userId: 'user', username: 'saved-user', token: 'old-token' }; + const state = {}; + let cleared = false; + const controller = new exported.SettingsController({ + sessionStore: { load: async () => record, clear: async () => { cleared = true; } }, + remoteState: { + setAccountUserId: value => { state.userId = value; }, + setAccountUsername: value => { state.username = value; }, + setStatusText: value => { state.status = value; }, + }, + }); + await controller.restoreCloudAccountSession(); + assert.equal(state.userId, ''); + assert.equal(state.username, 'saved-user'); + assert.equal(state.status, 'remote.settings.relayReauthorizationRequired'); + assert.equal(controller.hasCloudAccountSession(), false); + assert.equal(controller.activeCloudAccount(), undefined); + assert.equal(cleared, false); + assert.equal(record.token, 'old-token'); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/connection-health.test.cjs b/src/apps/mobile/harmonyos/tools/tests/connection-health.test.cjs new file mode 100644 index 0000000000..7d8055bda9 --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/connection-health.test.cjs @@ -0,0 +1,218 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +function load(name, deps = {}) { + const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets', name + '.ets'), 'utf8'); + const js = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText; + const exported = {}; + new Function('require', 'exports', js)(name => deps[name] || {}, exported); + return exported; +} +const factory = load('services/RemoteCommandFactory'); +const { RemoteSessionManager } = load('services/RemoteSessionManager', { './RemoteCommandFactory': factory }); +test('health probe forwards a short deadline and preserves transport failures', async () => { + const manager = Object.create(RemoteSessionManager.prototype); + const calls = []; + manager.send = async (command, timeoutMs) => { calls.push({ command, timeoutMs }); return { resp: 'ok' }; }; + assert.equal(await manager.ping(), true); + assert.equal(calls[0].command.cmd, 'ping'); + assert.equal(calls[0].timeoutMs, 10000); + const timeout = new Error('Health probe timed out'); + manager.send = async () => { throw timeout; }; + await assert.rejects(manager.ping(), error => error === timeout); +}); + +test('health deadline includes transport preparation and ignores a late result', async context => { + context.mock.timers.enable({ apis: ['setTimeout'] }); + const manager = Object.create(RemoteSessionManager.prototype); + let complete; + manager.send = () => new Promise(resolve => { complete = resolve; }); + const pending = manager.ping(); + const rejected = assert.rejects(pending, /health probe timed out/); + context.mock.timers.tick(10000); + await rejected; + complete({ resp: 'ok' }); + manager.send = async () => ({ resp: 'ok' }); + assert.equal(await manager.ping(), true); +}); + +const { AsyncLifecycleGate } = load('services/AsyncLifecycleGate'); +const { RemoteActivityViewModel } = load('pages/viewmodel/RemoteActivityViewModel', { + '../../i18n/RemoteI18n': { RemoteI18n: { t: key => key } }, + '../../services/ConnectionErrorPolicy': { ConnectionErrorPolicy: { errorText: error => error.message } }, + '../../services/RemoteLogger': { RemoteLogger: { info() {} } }, +}); +function deferred() { + let resolve, reject; + const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); + return { promise, resolve, reject }; +} +function activityFixture() { + const probe = deferred(); + const events = []; + const hooks = { + isConnected: () => true, isReconnecting: () => false, + isBusy: () => false, hasRemoteBinding: () => true, + isRemoteChat: () => false, activeSession: () => ({ sessionId: '' }), + onConnectionState: state => events.push(['state', state]), + onStatus: status => events.push(['status', status]), + onConnectionError: async () => false, + onStartPolling() {}, onStopPolling() {}, onPoll: async () => {}, + onReconnect: async () => events.push(['reconnect']), + onRestoreSession: async () => {}, + }; + const connection = { ping: () => probe.promise }; + const vm = new RemoteActivityViewModel( + { stopHeartbeat() {} }, connection, new AsyncLifecycleGate(), hooks, + ); + return { vm, probe, events, hooks, connection }; +} +test('foreground keeps a healthy connection quiet until the host probe completes', async () => { + const f = activityFixture(); + const pending = f.vm.verifyForeground(); + assert.deepEqual(f.events, []); + f.probe.resolve(true); + await pending; + assert.deepEqual(f.events, [['state', 'connected'], ['status', 'connection.connected']]); +}); +test('foreground starts reconnect only after a failed host probe', async () => { + const f = activityFixture(); + const pending = f.vm.verifyForeground(); + assert.deepEqual(f.events, []); + f.probe.reject(new Error('Host unavailable')); + await pending; + assert.deepEqual(f.events, [['status', 'Host unavailable'], ['state', 'reconnecting'], ['reconnect']]); +}); +for (const method of ['verifyForeground', 'checkConnectionHealth']) { + test(`${method} ignores an old target probe failure`, async () => { + const f = activityFixture(); + const pending = f.vm[method](); + f.vm.invalidate(); + f.probe.reject(new Error('Old target unavailable')); + await pending; + assert.deepEqual(f.events, []); + }); + test(`${method} rechecks ownership after asynchronous error handling`, async () => { + const f = activityFixture(); + const handler = deferred(); + f.hooks.onConnectionError = () => handler.promise; + const pending = f.vm[method](); + f.probe.reject(new Error('Old target unavailable')); + await Promise.resolve(); + f.vm.invalidate(); + handler.resolve(false); + await pending; + assert.deepEqual(f.events, []); + }); +} + +test('a transcript refresh failure after successful ping does not report a broken connection', async () => { + const f = activityFixture(); + f.hooks.isRemoteChat = () => true; + f.hooks.activeSession = () => ({ sessionId: 'active' }); + f.hooks.onPoll = async () => { throw new Error('Transcript refresh failed'); }; + const pending = f.vm.verifyForeground(); + f.probe.resolve(true); + await pending; + assert.deepEqual(f.events, [['state', 'connected'], ['status', 'connection.connected'], ['status', 'Transcript refresh failed']]); +}); + +test('invalidating an old recovery permits health checks for the new target', async () => { + const f = activityFixture(); + const recovery = deferred(); + f.hooks.isConnected = () => false; + f.hooks.isReconnecting = () => true; + let reconnects = 0; + f.connection.ping = () => { reconnects++; return recovery.promise; }; + const old = f.vm.checkConnectionHealth(); + assert.equal(reconnects, 1); + f.vm.invalidate(); + const current = f.vm.checkConnectionHealth(); + assert.equal(reconnects, 2); + recovery.resolve(); + await Promise.all([old, current]); +}); + +test('old recovery completion cannot clear the new target recovery guard', async () => { + const f = activityFixture(); + const first = deferred(); + const second = deferred(); + f.hooks.isConnected = () => false; + f.hooks.isReconnecting = () => true; + let reconnects = 0; + f.connection.ping = () => (++reconnects === 1 ? first.promise : second.promise); + const old = f.vm.checkConnectionHealth(); + f.vm.invalidate(); + const current = f.vm.checkConnectionHealth(); + first.resolve(); + await old; + await f.vm.checkConnectionHealth(); + assert.equal(reconnects, 2); + second.resolve(); + await current; +}); + +const heartbeatModule = load('services/RemoteHeartbeatController'); +const { RemoteActivityLifecycleController } = load('services/RemoteActivityLifecycleController', { + './RemoteHeartbeatController': heartbeatModule, +}); +test('foreground monitors host liveness independently of relay and stops on suspension', async () => { + const f = activityFixture(); + let tick; + let stopped = false; + let calls = 0; + const pendingProbe = deferred(); + const scheduler = { + setInterval(callback, interval) { assert.equal(interval, 15000); tick = callback; return 1; }, + clearInterval() { stopped = true; tick = undefined; }, + }; + let vm; + const activity = new RemoteActivityLifecycleController(() => vm.checkConnectionHealth(), 15000, scheduler); + vm = new RemoteActivityViewModel(activity, { ping: () => { calls++; return pendingProbe.promise; } }, + new AsyncLifecycleGate(), f.hooks); + vm.startHeartbeat(); + assert.equal(activity.isHeartbeatRunning(), true); + tick(); tick(); + assert.equal(calls, 1, 'Slow host probes must not overlap'); + pendingProbe.reject(new Error('Host stopped answering while relay remains connected')); + await new Promise(resolve => setImmediate(resolve)); + assert.deepEqual(f.events, [ + ['status', 'Host stopped answering while relay remains connected'], ['state', 'reconnecting'], + ]); + vm.suspendKeepingActiveTurnPoll(); + assert.equal(stopped, true); + assert.equal(activity.isHeartbeatRunning(), false); +}); + +test('periodic recovery probes the existing target without reselecting or clearing the page', async () => { + const f = activityFixture(); + f.hooks.isConnected = () => false; + f.hooks.isReconnecting = () => true; + const pending = f.vm.checkConnectionHealth(); + assert.deepEqual(f.events, []); + f.probe.resolve(true); + await pending; + assert.deepEqual(f.events, [['state', 'connected'], ['status', 'connection.connected']]); +}); + +const { RemoteCompactHomePolicy } = load('pages/policy/RemoteCompactHomePolicy'); +test('home recovery retains cached rows without cold-load skeletons', () => { + for (const phase of ['connected', 'reconnecting', 'disconnected', 'failed']) { + assert.equal(RemoteCompactHomePolicy.shouldShowInitialLoading(phase, 3, true), false); + assert.equal(RemoteCompactHomePolicy.shouldShowRecentSessions(phase === 'connected', 3), true); + } + assert.equal(RemoteCompactHomePolicy.shouldShowInitialLoading('reconnecting', 0, true), false); + assert.equal(RemoteCompactHomePolicy.shouldShowInitialLoading('pairing', 0, false), true); + assert.equal(RemoteCompactHomePolicy.shouldShowInitialLoading('connected', 0, true), true); + assert.equal(RemoteCompactHomePolicy.shouldShowRecentSessions(false, 0), false); + assert.equal(RemoteCompactHomePolicy.shouldShowRecentSessions(true, 0), true); +}); + +test('recovery keeps selected device header geometry while unbound home stays empty', () => { + assert.equal(RemoteCompactHomePolicy.headerSubtitle('connected', 'Studio'), 'Studio'); + assert.equal(RemoteCompactHomePolicy.headerSubtitle('reconnecting', 'Studio'), 'Studio'); + assert.equal(RemoteCompactHomePolicy.headerSubtitle('idle', 'Studio'), ''); + assert.equal(RemoteCompactHomePolicy.headerSubtitle('reconnecting', ''), ''); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/device-directory-lifecycle.test.cjs b/src/apps/mobile/harmonyos/tools/tests/device-directory-lifecycle.test.cjs new file mode 100644 index 0000000000..3706be6f2e --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/device-directory-lifecycle.test.cjs @@ -0,0 +1,179 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); + +function load(relative, dependencies = {}) { + const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets', relative + '.ets'), 'utf8') + .replace(/@ObservedV2\s*/g, '').replace(/@Trace\s*/g, ''); + const js = ts.transpileModule(source, { compilerOptions: { + target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS + } }).outputText; + const exports = {}; + new Function('require', 'exports', js)(name => dependencies[name] || {}, exports); + return exports; +} +const stateModule = load('pages/state/DeviceDirectoryState'); +const { DeviceDirectoryViewModel } = load('pages/viewmodel/DeviceDirectoryViewModel', { + '../state/DeviceDirectoryState': stateModule, + '../../services/RemoteLogger': { RemoteLogger: { info() {}, warn() {} } }, + '../../services/RemoteSessionIdentity': load('services/RemoteSessionIdentity'), + '../../services/DeviceDirectoryCoordinator': { DeviceDirectoryCoordinator: class { + constructor(source) { this.source = source; } + loadWorkspaceCatalog(id) { return this.source.listWorkspaceCatalog(id); } + } } +}); +function deferred() { + let resolve, reject; + const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); + return { promise, resolve, reject }; +} +function fixture(store) { + const state = new stateModule.DeviceDirectoryState(); + const calls = []; + let binding = { session: { token: 'token-a', userId: 'account-a', masterKey: new Uint8Array(32) }, relayUrl: 'relay' }, activeId = ''; + const vm = new DeviceDirectoryViewModel(state, { + credentials: () => binding, activeDeviceId: () => activeId, activeDeviceConnected: () => !!activeId + }, store, { listWorkspaceCatalog: () => { + const call = deferred(); calls.push(call); return call.promise; + } }); + vm.syncDevices([{ deviceId: 'desktop', deviceName: 'Desktop', online: true }]); + return { state, vm, calls, account(value) { binding = value; }, active(value) { activeId = value; } }; +} +const catalog = path => ({ source: 'opened', workspaces: [{ path }], recentWorkspaces: [{ path }] }); + +for (const outcome of ['success', 'failure']) { + test(`old account ${outcome} cannot publish or release a newer device request`, async () => { + const f = fixture(); + const old = f.vm.retryDevice('desktop'); + f.state.clear(); + f.account({ session: { token: 'token-b', userId: 'account-b', masterKey: new Uint8Array(32) }, relayUrl: 'relay' }); + f.vm.syncDevices([{ deviceId: 'desktop', online: true }]); + const current = f.vm.retryDevice('desktop'); + assert.equal(f.calls.length, 2); + if (outcome === 'success') f.calls[0].resolve(catalog('/old')); + else f.calls[0].reject(Error('Old connection failed')); + await old; + assert.equal(f.state.find('desktop').status, 'loading'); + assert.deepEqual(f.state.find('desktop').workspaces, []); + await f.vm.retryDevice('desktop'); + assert.equal(f.calls.length, 2, 'old finally must not remove the current request'); + f.calls[1].resolve(catalog('/new')); + await current; + assert.equal(f.state.find('desktop').status, 'ready'); + assert.equal(f.state.find('desktop').workspaces[0].path, '/new'); + }); +} + +test('account replacement invalidates reads even when the directory row is retained', async () => { + const f = fixture(); + const old = f.vm.retryDevice('desktop'); + f.account({ session: { token: 'token-b', userId: 'account-b', masterKey: new Uint8Array(32) }, relayUrl: 'relay' }); + const current = f.vm.retryDevice('desktop'); + assert.equal(f.calls.length, 2); + f.calls[0].reject(Error('Old account failure')); + await old; + assert.equal(f.state.find('desktop').status, 'loading'); + f.calls[1].resolve(catalog('/current')); + await current; + assert.equal(f.state.find('desktop').workspaces[0].path, '/current'); +}); + +test('live catalog takeover owns status despite a late directory error', async () => { + const f = fixture(); + const pending = f.vm.retryDevice('desktop'); + f.active('desktop'); + f.vm.beginLiveCatalog('desktop'); + f.vm.completeLiveCatalog('desktop', [], 'opened', [{ path: '/history' }]); + f.calls[0].reject(Error('Abandoned directory error')); + await pending; + assert.equal(f.state.find('desktop').status, 'ready'); + assert.equal(f.state.find('desktop').catalogSource, 'opened'); + assert.deepEqual(f.state.find('desktop').workspaces, []); +}); + +test('offline transition permits a fresh request without waiting for the old one', async () => { + const f = fixture(); + const old = f.vm.retryDevice('desktop'); + f.vm.syncDevices([{ deviceId: 'desktop', online: false }]); + assert.equal(f.state.find('desktop').status, 'idle'); + f.vm.syncDevices([{ deviceId: 'desktop', online: true }]); + const current = f.vm.retryDevice('desktop'); + assert.equal(f.calls.length, 2); + f.calls[1].resolve(catalog('/current')); await current; + f.calls[0].resolve(catalog('/stale')); await old; + assert.equal(f.state.find('desktop').workspaces[0].path, '/current'); +}); + +test('late cache cannot replace live session list or authoritative empty catalog', async () => { + const cache = deferred(); + const f = fixture({ loadWorkspaces: () => cache.promise, + loadList: async () => ({ sessions: [{ id: 'cached', workspacePath: '/closed' }] }) }); + const hydrate = f.vm.hydrateKnownDevices(); + f.vm.completeLiveCatalog('desktop', [], 'opened', []); + f.state.find('desktop').sessions = [{ id: 'live', deviceId: 'desktop' }]; + cache.resolve([{ path: '/closed' }]); await hydrate; + assert.deepEqual(f.state.find('desktop').workspaces, []); + assert.equal(f.state.find('desktop').sessions[0].id, 'live'); + assert.equal(f.state.find('desktop').status, 'ready'); +}); + +test('late cache never populates a signed-out account row', async () => { + const cache = deferred(); + const f = fixture({ loadWorkspaces: () => cache.promise, + loadList: async () => ({ sessions: [{ id: 'private' }] }) }); + const entry = f.state.find('desktop'); + const hydrate = f.vm.hydrateKnownDevices(); + f.state.clear(); f.account(undefined); + cache.resolve([{ path: '/private' }]); await hydrate; + assert.deepEqual(f.state.devices, []); + assert.deepEqual(entry.sessions, []); + assert.deepEqual(entry.workspaces, []); +}); + +test('same-path SSH workspace disclosure and load ownership remain independent', () => { + const state = new stateModule.DeviceDirectoryState(); + state.setWorkspaceExpanded('desktop', '/project', true, 'a', 'host-a'); + assert.equal(state.workspaceExpanded('desktop', '/project', 'a', 'host-a'), true); + assert.equal(state.workspaceExpanded('desktop', '/project', 'b', 'host-b'), false); + assert.equal(state.workspaceExpanded('desktop', '/project'), false); + const a = state.workspaceLoadState('desktop', '/project', 'a', 'host-a'); + const b = state.workspaceLoadState('desktop', '/project', 'b', 'host-b'); + assert.notEqual(a, b); + assert.equal(state.isCurrentWorkspaceLoad(a), true); + assert.equal(state.isCurrentWorkspaceLoad(b), true); +}); + +test('manual refresh preserves rows on failure, deduplicates clicks and clears error on retry', async () => { + const f = fixture(); + const pending = deferred(); let loads = 0, refreshes = 0; + f.vm.refreshExpandedWorkspaceSessions = async () => { refreshes++; }; + const oldRows = f.state.devices; + const first = f.vm.refreshDevices(() => { loads++; return pending.promise; }); + await f.vm.refreshDevices(async () => { loads++; }); + assert.equal(loads, 1); + assert.equal(f.state.refreshing, true); + pending.reject(new Error('Device directory offline')); await first; + assert.equal(f.state.devices, oldRows); + assert.equal(f.state.refreshError, 'Device directory offline'); + assert.equal(f.state.refreshing, false); + assert.equal(refreshes, 0); + await f.vm.refreshDevices(async () => {}); + assert.equal(f.state.refreshError, ''); + assert.equal(refreshes, 1); +}); + +test('old account manual refresh cannot fail or release replacement refresh', async () => { + const f = fixture(); const old = deferred(), current = deferred(); + f.vm.refreshExpandedWorkspaceSessions = async () => {}; + const first = f.vm.refreshDevices(() => old.promise); + f.state.clear(); + f.account({ session: { token: 'new', userId: 'new' }, relayUrl: 'relay' }); + const second = f.vm.refreshDevices(() => current.promise); + old.reject(new Error('Old failure')); await first; + assert.equal(f.state.refreshError, ''); + assert.equal(f.state.refreshing, true); + current.resolve(); await second; + assert.equal(f.state.refreshing, false); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/durable-session.test.cjs b/src/apps/mobile/harmonyos/tools/tests/durable-session.test.cjs index ae74671b4e..15f404114c 100644 --- a/src/apps/mobile/harmonyos/tools/tests/durable-session.test.cjs +++ b/src/apps/mobile/harmonyos/tools/tests/durable-session.test.cjs @@ -129,3 +129,162 @@ test('latest page beginning inside a fragmented event fetches its prefix', async assert.equal(h.applied[0].payload.id, 'split'); } finally { stream.close(); } }); + +test('backward pagination rejects a repeated fragment page instead of looping', async () => { + const h = harness([{ session_id: 'session', event: 'session-record', payload: { id: 'split' } }]); + let reads = 0; + h.transport.readBefore = async () => { + if (++reads > 2) throw Error('Unexpected additional history read'); + return { messages: [h.pages[1]], hasMore: true }; + }; + const stream = new DurableSessionStream('session', h.transport, h.callbacks); + try { + await settle(() => h.errors.length === 1); + assert.equal(reads, 2); + assert.match(h.errors[0].message, /pagination did not advance/); + assert.deepEqual(h.applied, []); + } finally { stream.close(); } +}); + +test('failure in a fragmented next record resumes after the last committed event without duplicating it', async () => { + const h = harness([ + { session_id: 'session', event: 'session-record', payload: { id: 'committed' } }, + { session_id: 'session', event: 'session-record', payload: { id: 'interrupted' } }, + ]); + const cursors = []; + const read = h.transport.read; + let failOnce = true; + h.transport.read = async (id, after) => { + cursors.push(after); + if (after === 3 && failOnce) { failOnce = false; throw new Error('Connection interrupted between fragments'); } + return read(id, after); + }; + const stream = new DurableSessionStream('session', h.transport, h.callbacks); + try { + await settle(() => h.errors.length === 1); + assert.deepEqual(h.applied.map(event => event.payload.id), ['committed']); + h.reconnect(); + await settle(() => h.caught === 1); + assert.deepEqual(cursors, [0, 1, 2, 3, 2, 3]); + assert.deepEqual(h.applied.map(event => event.payload.id), ['committed', 'interrupted']); + } finally { stream.close(); } +}); + +test('live records queued during backward history resume without losing the forward cursor', async () => { + const h = harness([ + { session_id: 'session', event: 'session-record', payload: { id: 'old' } }, + { session_id: 'session', event: 'session-record', payload: { id: 'new' } } + ]); + let releaseHistory; + let historyStarted = false; + const historyGate = new Promise(resolve => { releaseHistory = resolve; }); + h.transport.readBefore = async (_id, before) => { + if (before !== Number.MAX_SAFE_INTEGER) { historyStarted = true; await historyGate; } + const rows = h.pages.filter(row => row.seq < before).reverse(); + return { messages: rows.slice(0, 2), hasMore: rows.length > 2 }; + }; + const stream = new DurableSessionStream('session', h.transport, h.callbacks); + try { + await settle(() => h.caught === 1); + stream.loadOlder(); await settle(() => historyStarted); + const incoming = harness([{ session_id: 'session', event: 'session-record', payload: { id: 'live-final' } }]); + for (const row of incoming.pages) { + const fragment = JSON.parse(row.content.c); fragment.eventId = 'live-final'; + const next = { seq: row.seq + 4, content: { t: 'encrypted', c: JSON.stringify(fragment) } }; + h.pages.push(next); h.deliver(next); + } + releaseHistory(); + await settle(() => h.applied.length === 3); + assert.deepEqual(h.applied.map(event => event.payload.id), ['new', 'old', 'live-final']); + h.reconnect(); await settle(() => h.caught >= 3); + assert.equal(h.applied.length, 3); + assert.deepEqual(h.errors, []); + } finally { releaseHistory(); stream.close(); } +}); + +test('history failure exposes retry state and manual retry resumes the same cursor', async () => { + const h = harness([ + { session_id: 'session', event: 'session-record', payload: { id: 'old' } }, + { session_id: 'session', event: 'session-record', payload: { id: 'new' } } + ]); + const states = [], cursors = []; + let reject = true; + h.callbacks.onHistoryState = (loading, failed) => states.push([loading, failed]); + h.transport.readBefore = async (_id, before) => { + cursors.push(before); + if (before !== Number.MAX_SAFE_INTEGER && reject) throw new Error('History request failed'); + const rows = h.pages.filter(row => row.seq < before).reverse(); + return { messages: rows.slice(0, 2), hasMore: rows.length > 2 }; + }; + const stream = new DurableSessionStream('session', h.transport, h.callbacks); + try { + await settle(() => h.caught === 1); + stream.loadOlder(); await settle(() => h.errors.length === 1); + assert.deepEqual(states.at(-1), [false, true]); + assert.deepEqual(h.applied.map(event => event.payload.id), ['new']); + reject = false; + stream.loadOlder(); + assert.deepEqual(states.at(-1), [true, false]); + await settle(() => h.applied.length === 2 && states.at(-1)[0] === false); + assert.deepEqual(states.at(-1), [false, false]); + assert.deepEqual(cursors, [Number.MAX_SAFE_INTEGER, 3, 3]); + assert.deepEqual(h.applied.map(event => event.payload.id), ['new', 'old']); + } finally { stream.close(); } +}); + +test('older prefetch starts after the latest page and coalesces manual requests', async () => { + const timers = new Map(); let timerId = 0; + const module = {}; + new Function('require', 'exports', 'setTimeout', 'clearTimeout', compiled)( + () => ({ Encoding }), module, + (callback, delay) => { const id = ++timerId; timers.set(id, { callback, delay }); return id; }, + id => timers.delete(id)); + const h = harness([ + { session_id: 'session', event: 'session-record', payload: { id: 'old' } }, + { session_id: 'session', event: 'session-record', payload: { id: 'new' } } + ]); + const cursors = []; let release; + const gate = new Promise(resolve => { release = resolve; }); + h.transport.readBefore = async (_id, before) => { + cursors.push(before); + if (before !== Number.MAX_SAFE_INTEGER) await gate; + const rows = h.pages.filter(row => row.seq < before).reverse(); + return { messages: rows.slice(0, 2), hasMore: rows.length > 2 }; + }; + const stream = new module.DurableSessionStream('session', h.transport, h.callbacks); + try { + await settle(() => h.caught === 1); + assert.deepEqual(h.applied.map(event => event.payload.id), ['new']); + const timer = [...timers.entries()].find(([, value]) => value.delay === 250); + assert.ok(timer, 'latest page must schedule older prefetch'); + timers.delete(timer[0]); timer[1].callback(); + await settle(() => cursors.length === 2); + stream.loadOlder(); stream.loadOlder(); + assert.equal(cursors.length, 2, 'manual clicks share the background history read'); + release(); await settle(() => h.applied.length === 2); + assert.deepEqual(h.applied.map(event => event.payload.id), ['new', 'old']); + assert.equal(timers.size, 0, 'exhausted history must not poll'); + } finally { release(); stream.close(); } +}); + +test('closing a session cancels scheduled history prefetch', async () => { + const timers = new Map(); let timerId = 0; + const module = {}; + new Function('require', 'exports', 'setTimeout', 'clearTimeout', compiled)( + () => ({ Encoding }), module, + (callback, delay) => { const id = ++timerId; timers.set(id, { callback, delay }); return id; }, + id => timers.delete(id)); + const h = harness([{ session_id: 'session', event: 'session-record', payload: { id: 'new' } }]); + h.transport.readBefore = async () => ({ messages: h.pages, hasMore: true }); + const stream = new module.DurableSessionStream('session', h.transport, h.callbacks); + try { + await settle(() => h.caught === 1); + assert.equal(timers.size, 1); + const staleCallback = [...timers.values()][0].callback; + stream.close(); + assert.equal(timers.size, 0); + staleCallback(); + assert.equal(h.caught, 1); + assert.equal(timers.size, 0); + } finally { stream.close(); } +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/host-catalog.test.cjs b/src/apps/mobile/harmonyos/tools/tests/host-catalog.test.cjs index 99bfc9d840..8f1ecf1548 100644 --- a/src/apps/mobile/harmonyos/tools/tests/host-catalog.test.cjs +++ b/src/apps/mobile/harmonyos/tools/tests/host-catalog.test.cjs @@ -23,7 +23,17 @@ test('host catalog bursts coalesce, reconnect resets invalidation and unchanged await c.onResumed(); await c.onCaughtUp(); assert.equal(reads,1); await c.onCaughtUp(); assert.equal(reads,1); await c.onResumed(); await c.onCaughtUp(); assert.equal(reads,2); - f.observer.start('host'); await Promise.resolve(); assert.equal(f.streams.length,1); assert.equal(reads,3); + f.observer.start('host'); await Promise.resolve(); assert.equal(f.streams.length,1); assert.equal(reads,2); +}); +test('a session refresh can ensure catalog observation without scheduling itself again', async()=>{ + let reads=0; + const f=fixture(async()=>{reads++; if(reads<5) f.observer.start('host');}); + f.observer.start('host'); + await f.streams[0].callbacks.onCaughtUp(); + assert.equal(reads,1); + await f.streams[0].callbacks.onEvent({event:'host-catalog-changed'}); + await f.streams[0].callbacks.onCaughtUp(); + assert.equal(reads,2); }); test('switching runtime closes old catalog and ignores late old callbacks', async()=>{ let reads=0; const f=fixture(async()=>{reads++;}); f.observer.start('a'); const old=f.streams[0]; diff --git a/src/apps/mobile/harmonyos/tools/tests/interaction-mailbox.test.cjs b/src/apps/mobile/harmonyos/tools/tests/interaction-mailbox.test.cjs new file mode 100644 index 0000000000..497d08fe83 --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/interaction-mailbox.test.cjs @@ -0,0 +1,214 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +function load(name, dependencies = {}) { + const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets/services', `${name}.ets`), 'utf8'); + const exports = {}; + new Function('require', 'exports', ts.transpileModule(source, { compilerOptions: { + target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS, + } }).outputText)(key => dependencies[key] || {}, exports); + return exports; +} +const models = load('../model/InteractionMailbox'); +const { InteractionMailboxStore } = load('InteractionMailboxStore', { '../model/InteractionMailbox': models }); +const { ChatSessionController } = load('ChatSessionController', { + './InteractionMailboxStore': { InteractionMailboxStore }, './DurableSessionReducer': load('DurableSessionReducer'), +}); +const request = (id = 'approval', sessionId = 's') => ({ requestId: id, sessionId, + toolCallId: 'tool', action: 'write', resources: ['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/remote/file'], source: { identity: 'Agent' } }); +const snapshot = (requests = [request()], questions = [], sessionId = 's') => ({ sessionId, + permissions: { revision: 1, requests }, userQuestions: { revision: 1, questions } }); +function fixture() { + const f = { state: null, wire: snapshot(), calls: [], fail: false }; + f.client = { + async getModelCatalog() { return { version: 1, models: [], default_models: {} }; }, + async hostInvoke(command, args) { + f.calls.push({ command, args }); + if (f.fail) throw Error('offline'); + if (command === 'get_session_interaction_mailbox') return f.wire; + f.wire = snapshot([]); return {}; + }, + async startQuestionInteraction(sessionId, toolId) { f.calls.push({ command: 'start', sessionId, toolId }); }, + async answerQuestion(toolId, answers) { f.calls.push({ command: 'answer', toolId, answers }); f.wire = snapshot([]); }, + }; + f.store = new InteractionMailboxStore(f.client, state => { f.state = state; }); + f.store.select('s'); + return f; +} +const action = (type, id = 'approval', extra = {}) => ({ sessionId: 's', type, id, ...extra }); +const deferred = () => { let resolve; const promise = new Promise(r => { resolve = r; }); return { promise, resolve }; }; + +test('mailbox identity preserves distinct requests sharing a tool and filters foreign questions', async () => { + const f = fixture(); + f.wire = snapshot([request('one'), request('two'), request('foreign', 'other')], [ + { toolId: 'question', sessionId: 's', questions: {} }, { toolId: 'foreign', sessionId: 'other', questions: {} }, + ]); + await f.store.refresh(); + assert.deepEqual(f.state.requests.map(r => r.requestId), ['one', 'two']); + assert.deepEqual(f.state.requests[0].resources, ['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/remote/file']); + assert.equal(f.state.requests[0].action, 'write'); + assert.deepEqual(f.state.questions.map(q => q.toolId), ['question']); +}); + +test('tool completion cannot consume runtime approval or manufacture an active turn', async () => { + const f = fixture(); let callbacks, mailbox, transcript; + f.client.subscribeSession = (_id, next) => { callbacks = next; return { close() {}, wake() {} }; }; + const controller = new ChatSessionController(f.client, { canPoll: () => true, + onSnapshot: value => { transcript = value; }, onMailbox: value => { mailbox = value; }, onError: error => { throw error; } }); + controller.start('s', { pollVersion: 0, knownMessageCount: 0, knownModelCatalogVersion: 0 }); + await callbacks.onResumed(); await callbacks.onCaughtUp(); + await callbacks.onEvent({ session_id: 's', event: 'agentic://tool-event', payload: { + turnId: 'turn', toolEvent: { event_type: 'Completed', tool_id: 'tool' }, + } }); + assert.deepEqual(mailbox.requests.map(r => r.requestId), ['approval']); + assert.equal(transcript.activeTurn, undefined); + assert.deepEqual(transcript.messageSnapshot, []); + f.wire = snapshot([]); + await callbacks.onEvent({ session_id: 's', event: 'session-interaction-changed', payload: {} }); + assert.deepEqual(mailbox.requests, []); +}); + +test('slow mailbox reads do not hold transcript catch-up or event delivery', async () => { + for (const trigger of ['resume', 'invalidation']) { + const f = fixture(), gate = deferred(); let callbacks, finished = false, transcript; + f.client.hostInvoke = async () => gate.promise; + f.client.subscribeSession = (_id, next) => { callbacks = next; return { close() {}, wake() {} }; }; + const controller = new ChatSessionController(f.client, { canPoll: () => true, + onSnapshot: value => { transcript = value; }, onError: error => { throw error; } }); + controller.start('s', { pollVersion: 0, knownMessageCount: 0, knownModelCatalogVersion: 0 }); + const delivery = (trigger === 'resume' ? callbacks.onResumed() : + callbacks.onEvent({ session_id: 's', event: 'session-interaction-changed', payload: {} })) + .then(() => { finished = true; }); + await Promise.resolve(); await Promise.resolve(); + try { + assert.equal(finished, true, 'Mailbox RPC must not block the serial transcript stream'); + await callbacks.onCaughtUp(); + assert.ok(transcript); + } finally { + gate.resolve(snapshot()); await delivery; controller.stop(); + } + } +}); + +test('edit approval sends request identity and object patch then refreshes authority', async () => { + const f = fixture(); await f.store.refresh(); + await f.store.dispatch(action('approve', 'approval', { updatedInput: { path: '/new' } })); + assert.deepEqual(f.calls[1], { command: 'respond_permission', args: { + request: { requestId: 'approval', reply: 'once', updatedInput: { path: '/new' } }, + } }); + assert.deepEqual(f.state.requests, []); assert.equal(f.state.busy, false); + assert.equal(f.state.ownsToolInteraction('tool'), true); + assert.equal(f.state.ownsToolInteraction('approval'), false); + f.store.select('another'); + assert.equal(f.state.ownsToolInteraction('tool'), false); +}); + +test('scalar and array edits are rejected without a remote mutation', async () => { + for (const updatedInput of [null, [], true, 3, 'text']) { + const f = fixture(); await f.store.refresh(); + await f.store.dispatch(action('approve', 'approval', { updatedInput })); + assert.equal(f.calls.length, 1); assert.equal(f.state.failure, 'reply'); + assert.equal(f.state.requests.length, 1); assert.equal(f.state.busy, false); + } +}); + +test('reject ignores an edited payload and duplicate taps cannot submit twice', async () => { + const f = fixture(); await f.store.refresh(); const gate = deferred(); + const invoke = f.client.hostInvoke; + f.client.hostInvoke = async (command, args) => { if (command === 'respond_permission') await gate.promise; return invoke(command, args); }; + const first = f.store.dispatch(action('reject', 'approval', { updatedInput: { bad: 'ignored' } })); + await f.store.dispatch(action('approve')); + gate.resolve(); await first; + const replies = f.calls.filter(c => c.command === 'respond_permission'); + assert.equal(replies.length, 1); assert.equal(replies[0].args.request.reply, 'reject'); + assert.equal(replies[0].args.request.updatedInput, undefined); +}); + +test('reject remains available even when the approval draft is not an object', async () => { + for (const updatedInput of [null, [], true, 3, 'unfinished draft']) { + const f = fixture(); await f.store.refresh(); + await f.store.dispatch(action('reject', 'approval', { updatedInput })); + const reply = f.calls.find(c => c.command === 'respond_permission'); + assert.ok(reply, 'Reject must not validate an unused approval draft'); + assert.equal(reply.args.request.reply, 'reject'); + assert.equal(reply.args.request.updatedInput, undefined); + assert.equal(f.state.failure, ''); + } +}); + +test('reconnect refreshes the permission queue and stale request actions stay inert', async () => { + const f = fixture(); + f.wire = snapshot([request('one'), request('two')]); + await f.store.refresh(); + f.fail = true; + await f.store.refresh(); + assert.deepEqual(f.state.requests.map(r => r.requestId), ['one', 'two']); + // Another controller answers one request while this controller is offline. + f.wire = snapshot([request('two')]); f.fail = false; + await f.store.refresh(); + await f.store.dispatch(action('approve', 'one')); + assert.equal(f.calls.filter(c => c.command === 'respond_permission').length, 0); + await f.store.dispatch(action('reject', 'two')); + assert.equal(f.calls.find(c => c.command === 'respond_permission').args.request.requestId, 'two'); + assert.deepEqual(f.state.requests, []); +}); + +test('read and reply errors retain controls and can recover through retry', async () => { + const f = fixture(); await f.store.refresh(); f.fail = true; + await f.store.refresh(); assert.equal(f.state.failure, 'load'); assert.equal(f.state.requests.length, 1); + await f.store.dispatch(action('approve')); assert.equal(f.state.failure, 'reply'); assert.equal(f.state.requests.length, 1); + f.fail = false; await f.store.dispatch(action('refresh')); + assert.equal(f.state.failure, ''); assert.equal(f.state.requests.length, 1); +}); + +test('session mismatch fails visibly without replacing a known mailbox', async () => { + const f = fixture(); await f.store.refresh(); f.wire = snapshot([], [], 'other'); + await f.store.refresh(); assert.equal(f.state.failure, 'load'); assert.equal(f.state.requests.length, 1); +}); + +test('invalidation during a read discards the obsolete result and coalesces a fresh read', async () => { + const f = fixture(), gate = deferred(); let reads = 0; + f.client.hostInvoke = async () => ++reads === 1 ? gate.promise : snapshot([request('fresh')]); + const first = f.store.refresh(); const second = f.store.refresh(); + gate.resolve(snapshot([request('old')])); await Promise.all([first, second]); + assert.equal(reads, 2); assert.equal(f.state.requests[0].requestId, 'fresh'); +}); + +test('late reads and replies cannot repopulate a switched or signed-out session', async () => { + for (const operation of ['read', 'reply']) { + const f = fixture(); await f.store.refresh(); const gate = deferred(); + f.client.hostInvoke = async () => gate.promise; + const running = operation === 'read' ? f.store.refresh() : f.store.dispatch(action('approve')); + f.store.select(''); gate.resolve(snapshot()); await running; + assert.equal(f.state.sessionId, ''); assert.deepEqual(f.state.requests, []); assert.equal(f.state.busy, false); + } +}); + +test('question interaction starts once per selection and answers refresh the mailbox', async () => { + const f = fixture(); f.wire = snapshot([], [{ toolId: 'q', sessionId: 's', questions: {} }]); await f.store.refresh(); + await f.store.dispatch(action('start_question', 'q')); await f.store.dispatch(action('start_question', 'q')); + assert.equal(f.calls.filter(c => c.command === 'start').length, 1); + await f.store.dispatch(action('answer', 'q', { answers: { question: 'Yes' } })); + assert.equal(f.calls.filter(c => c.command === 'answer').length, 1); assert.deepEqual(f.state.questions, []); + await f.store.dispatch(action('answer', 'q', { answers: {} })); + assert.equal(f.calls.filter(c => c.command === 'answer').length, 1); +}); + +test('invalidation queued during a failed read still fetches the current mailbox', async () => { + const f = fixture(); await f.store.refresh(); + let rejectRead, reads = 0; + f.client.hostInvoke = async () => { + reads++; + if (reads === 1) return new Promise((_resolve, reject) => { rejectRead = reject; }); + return snapshot([request('current')]); + }; + const pending = f.store.refresh(); + f.store.refresh(); + rejectRead(new Error('Old connection closed')); + await pending; + assert.equal(reads, 2); + assert.deepEqual(f.state.requests.map(item => item.requestId), ['current']); + assert.equal(f.state.failure, ''); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/permission-mode.test.cjs b/src/apps/mobile/harmonyos/tools/tests/permission-mode.test.cjs new file mode 100644 index 0000000000..adfcfd256c --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/permission-mode.test.cjs @@ -0,0 +1,39 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +function load(name, deps = {}) { + const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets', name + '.ets'), 'utf8'); + const js = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText; + const exported = {}; + new Function('require', 'exports', js)(name => deps[name] || {}, exported); + return exported; +} +const factory = load('services/RemoteCommandFactory'); +const { RemoteSessionManager } = load('services/RemoteSessionManager', { './RemoteCommandFactory': factory }); +test('permission save displays host-confirmed mode', async () => { + const manager = Object.create(RemoteSessionManager.prototype); + manager.send = async () => ({ resp: 'ok', mode: 'auto' }); + assert.equal(await manager.setPermissionMode('full_access'), 'auto'); +}); + +test('legacy permission acknowledgement is followed by an authoritative read', async () => { + const manager = Object.create(RemoteSessionManager.prototype); + const calls = []; + manager.send = async command => { + calls.push(command.cmd); + return command.cmd === 'set_permission_mode' ? { resp: 'ok' } : { resp: 'ok', mode: 'ask' }; + }; + assert.equal(await manager.setPermissionMode('full_access'), 'ask'); + assert.deepEqual(calls, ['set_permission_mode', 'get_permission_mode']); +}); + +test('missing or future permission modes never masquerade as approval policy', async () => { + const manager = Object.create(RemoteSessionManager.prototype); + for (const mode of [undefined, 'future_mode']) { + manager.send = async () => ({ resp: 'ok', mode }); + await assert.rejects(manager.getPermissionMode(), /Permission mode unavailable/); + await assert.rejects(manager.setPermissionMode('full_access'), /Permission mode unavailable/); + } +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/session-model-hydration.test.cjs b/src/apps/mobile/harmonyos/tools/tests/session-model-hydration.test.cjs new file mode 100644 index 0000000000..25c80356b2 --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/session-model-hydration.test.cjs @@ -0,0 +1,69 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets/services/ChatSessionController.ets'), 'utf8'); +const exported = {}; +new Function('require', 'exports', ts.transpileModule(source, {compilerOptions: {target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS}}).outputText)(name => ({ + './InteractionMailboxStore': { InteractionMailboxStore: class { select() {} } }, + './DurableSessionReducer': { DurableSessionReducer: class { messages() { return []; } } }, +}[name] || {}), exported); +const flush = () => new Promise(resolve => setImmediate(resolve)); +function fixture() { + const requests = [], streams = [], snapshots = [], errors = []; + const manager = { + getModelCatalog(sessionId) { return new Promise((resolve, reject) => requests.push({sessionId, resolve, reject})); }, + subscribeSession(sessionId, callbacks) { streams.push(callbacks); return {close() {}, isClosed() {return false;}}; }, + }; + const controller = new exported.ChatSessionController(manager, {canPoll: () => true, onSnapshot: value => snapshots.push(value), onError: error => errors.push(error)}); + const start = id => controller.start(id, {pollVersion: 0, knownMessageCount: 0, knownModelCatalogVersion: 0}); + return {controller, start, requests, streams, snapshots, errors}; +} +test('initial model read does not block transcript and cannot cross session navigation', async () => { + const f = fixture(); + f.start('old'); await f.streams[0].onCaughtUp(); + assert.equal(f.snapshots.length, 1); + assert.equal(f.requests[0].sessionId, 'old'); + f.start('new'); await f.streams[1].onCaughtUp(); + f.requests[0].resolve({version: 1, session_model_id: 'old-model'}); await flush(); + assert.equal(f.snapshots.some(x => x.modelCatalog), false); + f.requests[1].resolve({version: 2, session_model_id: 'new-model'}); await flush(); + assert.equal(f.snapshots.at(-1).modelCatalog.session_model_id, 'new-model'); + assert.equal(f.snapshots.at(-1).sessionId, 'new'); + f.controller.stop(); +}); +test('catalog waits for initial records and is delivered once', async () => { + const f = fixture(); f.start('s'); + f.requests[0].resolve({version: 2, session_model_id: 'model'}); await flush(); + assert.equal(f.snapshots.length, 0); + await f.streams[0].onCaughtUp(); + assert.equal(f.snapshots.at(-1).modelCatalog.session_model_id, 'model'); + await f.streams[0].onCaughtUp(); + assert.equal(f.snapshots.at(-1).modelCatalog, undefined); + f.controller.stop(); +}); +test('confirmed model changes fence a pending initial read', async () => { + const f = fixture(); f.start('s'); await f.streams[0].onCaughtUp(); + f.controller.updateKnownModelCatalogVersion(3); + f.requests[0].resolve({version: 2, session_model_id: 'old-model'}); await flush(); + assert.equal(f.snapshots.some(x => x.modelCatalog), false); + f.controller.stop(); +}); +test('late catalog failure after stop is ignored', async () => { + const f = fixture(); f.start('s'); f.controller.stop(); + f.requests[0].reject(Error('offline')); await flush(); + assert.equal(f.errors.length, 0); +}); + +test('catalog invalidation refreshes the current session and supersedes an older read', async () => { + const f = fixture(); f.start('s'); await f.streams[0].onCaughtUp(); + const pending = f.controller.refreshModelCatalog(); + assert.equal(f.requests[1].sessionId, 's'); + f.requests[0].resolve({version: 1, session_model_id: 'stale'}); await flush(); + assert.equal(f.snapshots.some(x => x.modelCatalog), false); + f.requests[1].resolve({version: 2, session_model_id: 'external'}); await pending; + assert.equal(f.snapshots.at(-1).modelCatalog.session_model_id, 'external'); + f.controller.stop(); await f.controller.refreshModelCatalog(); + assert.equal(f.requests.length, 2); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/session-record.test.cjs b/src/apps/mobile/harmonyos/tools/tests/session-record.test.cjs index 3093f2e972..ac072f7014 100644 --- a/src/apps/mobile/harmonyos/tools/tests/session-record.test.cjs +++ b/src/apps/mobile/harmonyos/tools/tests/session-record.test.cjs @@ -10,6 +10,7 @@ function load(name, dependencies = {}) { new Function('require', 'exports', compiled)(name => dependencies[name] || {}, exported); return exported; } +const mailboxModule = load('InteractionMailboxStore', { '../model/InteractionMailbox': load('../model/InteractionMailbox') }); const reducerModule = load('DurableSessionReducer'); const { DurableSessionReducer } = reducerModule; function record(revision, content, status = 'inprogress', itemId = 'text') { @@ -40,9 +41,9 @@ test('late older child cannot regress completed parent, while its own unseen con assert.equal(reducer.messages()[1].renderVersion, 10); }); test('controller hydrates from the same record log without transcript RPC', async () => { - const { ChatSessionController } = load('ChatSessionController', { './DurableSessionReducer': reducerModule, './PermissionControlOverlay': load('PermissionControlOverlay') }); + const { ChatSessionController } = load('ChatSessionController', { './DurableSessionReducer': reducerModule, './InteractionMailboxStore': mailboxModule }); let callbacks, snapshots = []; - const manager = { subscribeSession: (_id, next) => { callbacks = next; return { wake() {}, close() {} }; }, getSessionMessages: () => { throw new Error('Snapshot RPC is forbidden'); } }; + const manager = { getModelCatalog: async () => ({ version: 1, models: [], default_models: {} }), subscribeSession: (_id, next) => { callbacks = next; return { wake() {}, close() {} }; }, getSessionMessages: () => { throw new Error('Snapshot RPC is forbidden'); } }; const controller = new ChatSessionController(manager, { onSnapshot: value => snapshots.push(value), canPoll: () => true, onError: error => { throw error; } }); controller.start('session', { pollVersion: 0, knownMessageCount: 0, knownModelCatalogVersion: 0 }); await callbacks.onEvent(record(1, 'partial')); @@ -78,55 +79,94 @@ test('command history entrypoints delegate to the same durable collection with n await controller.loadMessages('stale', () => false); await controller.reloadMessages('s1', () => true); await controller.loadOlderMessages('s1', 0, true, false); + await controller.loadOlderMessages('s1', 0, true, true); await controller.loadOlderMessages('s1', 0, false, false); - assert.deepEqual(requests, [['s1', false], ['s1', false], ['s1', true]]); + await controller.loadOlderMessages('', 0, true, true); + assert.deepEqual(requests, [['s1', false], ['s1', false], ['s1', true], ['s1', true]]); assert.equal(rpc, 0); assert.equal(cacheReads, 0); }); -test('permission controls decorate active tools separately and authoritative mailbox removes resolved requests', () => { - const { PermissionControlOverlay } = load('PermissionControlOverlay'); - const overlay = new PermissionControlOverlay(); - const base = [{ id: 'a', role: 'assistant', status: 'active', turnId: 't', text: '', tools: [{id:'tool', name:'Write', tool_input:{content:'old'}}], items: [] }]; - overlay.apply({event:'agentic://tool-event',payload:{turnId:'t',toolEvent:{event_type:'ConfirmationNeeded',tool_id:'tool',params:{content:'new'}}}}); - assert.equal(overlay.decorate(base)[0].tools[0].status, 'pending_confirmation'); - assert.equal(overlay.decorate(base)[0].tools[0].tool_input.content, 'new'); - assert.equal(base[0].tools[0].status, undefined); - overlay.hydrate('session', []); - assert.equal(overlay.decorate(base)[0].tools[0].status, undefined); - overlay.hydrate('session', [{requestId:'req',sessionId:'session',toolCallId:'tool',source:{identity:'Write'}}]); - assert.equal(overlay.decorate(base)[0].tools[0].tool_input.content, 'old'); - overlay.apply({event:'agentic://tool-event',payload:{turnId:'t',toolEvent:{event_type:'Rejected',tool_id:'tool'}}}); - assert.equal(overlay.decorate(base)[0].tools[0].status, undefined); -}); -test('canonical tool completion clears permission overlay without a duplicate typed completion event', () => { - const { PermissionControlOverlay } = load('PermissionControlOverlay'); - const overlay = new PermissionControlOverlay(); - overlay.apply({ event: 'agentic://tool-event', payload: { turnId: 'turn', toolEvent: { event_type: 'ConfirmationNeeded', tool_id: 'tool', tool_name: 'write' } } }); - overlay.hydrate('session', [{requestId:'permission', sessionId:'session', toolCallId:'tool', source:{identity:'write'}}]); - const messages = [{ id:'assistant-turn', turnId:'turn', role:'assistant', status:'active', tools:[{id:'tool',status:'completed'}], items:[] }]; - const decorated = overlay.decorate(messages); - assert.equal(decorated[0].tools[0].status, 'completed'); - assert.equal(decorated[0].tools[0].permission_request_id, undefined); -}); -test('initial and resumed mailbox restores a waiting question through the existing question tool presentation', async () => { - const { ChatSessionController } = load('ChatSessionController', { './DurableSessionReducer': reducerModule, './PermissionControlOverlay': load('PermissionControlOverlay') }); - let callbacks, snapshot, mailboxReads = 0; +test('initial and resumed mailbox restores questions independently of transcript lifetime', async () => { + const { ChatSessionController } = load('ChatSessionController', { './DurableSessionReducer': reducerModule, './InteractionMailboxStore': mailboxModule }); + let callbacks, snapshot, mailbox, mailboxReads = 0; const question = { questions: [{question:'Proceed?',options:[{label:'Yes'}]}] }; const manager = { + getModelCatalog: async () => ({ version: 1, models: [], default_models: {} }), subscribeSession: (_id, next) => { callbacks = next; return {wake(){},close(){}}; }, hostInvoke: async (command, args) => { assert.equal(command, 'get_session_interaction_mailbox'); assert.equal(args.request.sessionId, 'session'); mailboxReads++; return {sessionId:'session',permissions:{revision:0,requests:[]},userQuestions:{revision:1,questions:[{toolId:'question-tool',sessionId:'session',questions:question,registeredAtMs:1}]}}; } }; - const controller = new ChatSessionController(manager,{onSnapshot:value=>snapshot=value,onError:error=>{throw error;},canPoll:()=>true}); + const controller = new ChatSessionController(manager,{onSnapshot:value=>snapshot=value,onMailbox:value=>mailbox=value,onError:error=>{throw error;},canPoll:()=>true}); controller.start('session',{pollVersion:0,knownMessageCount:0,knownModelCatalogVersion:0}); await callbacks.onResumed(); await callbacks.onCaughtUp(); - assert.equal(snapshot.activeTurn.tools[0].name,'AskUserQuestion'); - assert.equal(snapshot.activeTurn.tools[0].id,'question-tool'); - assert.deepEqual(snapshot.activeTurn.tools[0].tool_input,question); + assert.equal(snapshot.activeTurn,undefined); + assert.equal(snapshot.cursor.knownMessageCount,0); + assert.equal(mailbox.questions[0].toolId,'question-tool'); + assert.deepEqual(mailbox.questions[0].questions,question); await callbacks.onResumed(); await callbacks.onCaughtUp(); - assert.equal(mailboxReads,2); assert.equal(snapshot.activeTurn.tools.length,1); + assert.equal(mailboxReads,2); assert.equal(mailbox.questions.length,1); await callbacks.onEvent({session_id:'session',event:'session-interaction-changed',payload:{sessionId:'session',userQuestionsRevision:2}}); - assert.equal(mailboxReads,3); assert.equal(snapshot.activeTurn.tools.length,1); + assert.equal(mailboxReads,3); assert.equal(mailbox.questions.length,1); +}); + +test('restoring a turn header does not restore children older than its tombstone', () => { + const reducer = new DurableSessionReducer(); + reducer.apply(record(1, 'deleted content')); + reducer.apply({session_id:'session',event:'session-record',payload:{sessionId:'session',id:'turn/turn',revision:10,deleted:true}}); + const header = record(11, ''); + header.payload.id = 'turn/turn'; delete header.payload.round; delete header.payload.item; + reducer.apply(header); + assert.equal(reducer.messages()[1].text, ''); + reducer.apply(record(9, 'late old child', 'inprogress', 'old-child')); + assert.equal(reducer.messages()[1].text, ''); + reducer.apply(record(12, 'new child', 'inprogress', 'new-child')); + assert.equal(reducer.messages()[1].text, 'new child'); +}); + +test('restoring a round with a new item does not resurrect its deleted siblings', () => { + const reducer = new DurableSessionReducer(); + reducer.apply(record(1, 'deleted sibling')); + reducer.apply({session_id:'session',event:'session-record',payload:{sessionId:'session',id:'round/round',revision:10,deleted:true}}); + reducer.apply(record(11, 'new sibling', 'inprogress', 'new-child')); + assert.equal(reducer.messages()[1].text, 'new sibling'); +}); + + +test('superseded and retry-superseded text thinking and tools do not reach presentation', () => { + for (const kind of ['text', 'thinking', 'tool']) for (const status of ['superseded', 'retry_superseded']) { + const reducer = new DurableSessionReducer(); + const event = record(1, 'obsolete'); + event.payload.item.type = kind; event.payload.item.data.status = status; + reducer.apply(event); + const assistant = reducer.messages()[1]; + assert.deepEqual(assistant.items, [], `${kind}/${status}`); + assert.equal(assistant.text, ''); assert.deepEqual(assistant.tools, []); + } +}); + +test('subagent session identity works without the legacy boolean marker', () => { + const reducer = new DurableSessionReducer(); + const event = record(1, 'child output'); + event.payload.item.data.subagentSessionId = 'child-session'; + reducer.apply(event); + const assistant = reducer.messages()[1]; + assert.equal(assistant.items[0].is_subagent, true); + assert.equal(assistant.text, ''); +}); + + +test('tool actions use call identity and preserve zero duration', () => { + const reducer = new DurableSessionReducer(); + const event = record(1, ''); + event.payload.item.type = 'tool'; + Object.assign(event.payload.item.data, { + toolName:'Read', toolCall:{id:'call-id',input:{path:'/test'}}, + toolResult:{success:true,result:'output',durationMs:9}, startTime:123, durationMs:0 + }); + reducer.apply(event); + const tool = reducer.messages()[1].tools[0]; + assert.equal(tool.id,'call-id'); assert.equal(tool.status,'completed'); + assert.equal(tool.start_ms,123); assert.equal(tool.duration_ms,0); }); diff --git a/src/apps/mobile/harmonyos/tools/tests/terminal-lifecycle.test.cjs b/src/apps/mobile/harmonyos/tools/tests/terminal-lifecycle.test.cjs new file mode 100644 index 0000000000..c586462c5d --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/terminal-lifecycle.test.cjs @@ -0,0 +1,102 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +function fixture() { + const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets'), 'utf8'); + const compiled = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText; + const exported = {}; + new Function('require', 'exports', compiled)(name => name.endsWith('WorkspaceFileUploadClient') ? { WorkspaceFileUploadClient: class {} } : {}, exported); + const state = { visible: true, terminalId: '', output: '', error: '' }; + const subscriptions = [], reads = []; + const manager = { + subscribeSession(id, callbacks) { subscriptions.push(callbacks); return { close() {} }; }, + hostInvoke(command, args) { return new Promise((resolve, reject) => reads.push({ args, resolve, reject })); }, + }; + const vm = new exported.WorkspaceToolsViewModel(state, manager, () => 'runtime'); + vm.owner = 'runtime'; + return { vm, state, subscriptions, reads }; +} +const flush = async () => { for (let i = 0; i < 8; i++) await Promise.resolve(); }; +const page = data => ({ data, nextOffset: data.length, cursor: data.length, truncated: false }); + +test('reattaching the same terminal rejects old stream callbacks and hands history reading to the new attachment', async () => { + const f = fixture(); + f.vm.attach('same'); + const stale = f.subscriptions[0]; + f.vm.attach('same'); + stale.onError(new Error('Old stream disconnected')); + await stale.onCaughtUp(); + assert.equal(f.state.error, ''); + assert.equal(f.reads.length, 1); + f.reads[0].resolve(page('stale output')); + await flush(); + assert.equal(f.state.output, ''); + assert.equal(f.reads.length, 2); + f.reads[1].resolve(page('current output')); + await flush(); + assert.equal(f.state.output, 'current output'); + f.subscriptions[1].onError(new Error('Current stream disconnected')); + assert.equal(f.state.error, 'Current stream disconnected'); +}); + +test('failed history from the previous terminal does not leak its error or block the selected terminal', async () => { + const f = fixture(); + f.vm.attach('first'); f.vm.attach('second'); + f.reads[0].reject(new Error('Old history unavailable')); + await flush(); + assert.equal(f.state.error, ''); + assert.equal(f.reads[1].args.sessionId, 'second'); + f.reads[1].resolve(page('second output')); + await flush(); + assert.equal(f.state.output, 'second output'); +}); + + +test('non-advancing terminal history fails once and preserves rendered output', async () => { + const f = fixture(); + f.vm.attach('first'); + f.reads[0].resolve({ data: 'hello', nextOffset: 5, cursor: 11, truncated: false }); + await flush(); + f.reads[1].resolve({ data: 'duplicate', nextOffset: 5, cursor: 11, truncated: false }); + await flush(); + assert.equal(f.reads.length, 2); + assert.equal(f.state.output, 'hello'); + assert.match(f.state.error, /made no progress/); +}); + +test('truncated history can reset its cursor and finish on an empty final page', async () => { + const f = fixture(); + f.vm.attach('first'); + f.reads[0].resolve(page('old output')); + await flush(); + const refresh = f.subscriptions[0].onCaughtUp(); + f.reads[1].resolve({ data: 'new', nextOffset: 3, cursor: 4, truncated: true }); + await flush(); + f.reads[2].resolve({ data: '!', nextOffset: 4, cursor: 4, truncated: false }); + await refresh; + assert.equal(f.state.output, 'new!'); + const final = f.subscriptions[0].onCaughtUp(); + f.reads[3].resolve({ data: '', nextOffset: 4, cursor: 4, truncated: false }); + await final; + assert.equal(f.state.output, 'new!'); + assert.equal(f.state.error, ''); +}); + + +test('empty recovered history clears its connection error without erasing unrelated errors', async () => { + const f = fixture(); + f.vm.attach('first'); f.reads[0].resolve(page('hello')); await flush(); + f.subscriptions[0].onError(new Error('Disconnected')); + const recovery = f.subscriptions[0].onCaughtUp(); + f.reads[1].resolve({data:'', nextOffset:5, cursor:5, truncated:false}); + await recovery; + assert.equal(f.state.error, ''); assert.equal(f.state.output, 'hello'); + f.subscriptions[0].onError(new Error('Disconnected')); + f.state.error = 'Input failed'; + const next = f.subscriptions[0].onCaughtUp(); + f.reads[2].resolve({data:'', nextOffset:5, cursor:5, truncated:false}); + await next; + assert.equal(f.state.error, 'Input failed'); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/workspace-catalog.test.cjs b/src/apps/mobile/harmonyos/tools/tests/workspace-catalog.test.cjs new file mode 100644 index 0000000000..055d2c16c3 --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/workspace-catalog.test.cjs @@ -0,0 +1,94 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); + +function load(name) { + const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets/services', `${name}.ets`), 'utf8'); + const exports = {}; + const js = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText; + new Function('require', 'exports', js)(dependency => load(dependency.replace('./', '')), exports); + return exports; +} +const { projectWorkspaceCatalog } = load('WorkspaceCatalog'); +const assistants = [{ path: '/assistant', name: 'Assistant' }]; + +test('empty opened catalog stays empty without losing picker history', () => { + const catalog = projectWorkspaceCatalog({ workspaces: [{ path: '/closed' }], opened_workspaces: [] }, assistants); + assert.equal(catalog.source, 'opened'); + assert.deepEqual(catalog.workspaces, []); + assert.equal(catalog.recentWorkspaces[0].path, '/closed'); +}); + +test('old hosts use explicitly identified recent fallback', () => { + for (const opened_workspaces of [undefined, null]) { + const catalog = projectWorkspaceCatalog({ workspaces: [{ path: '/history' }], opened_workspaces }, assistants); + assert.equal(catalog.source, 'recent'); + assert.deepEqual(catalog.workspaces.map(row => row.path), ['/assistant', '/history']); + } +}); + +test('assistant enrichment never changes remote identity and same-path SSH roots stay distinct', () => { + const catalog = projectWorkspaceCatalog({ opened_workspaces: [ + { path: '/assistant', name: 'folder' }, + { path: '/assistant', name: 'SSH A', remote_connection_id: 'a' }, + { path: '/assistant', name: 'SSH B', remote_connection_id: 'b' }, + { path: '/assistant', name: 'duplicate' }, + ] }, assistants); + assert.equal(catalog.source, 'opened'); + assert.deepEqual(catalog.workspaces.map(row => row.name), ['Assistant', 'SSH A', 'SSH B']); + assert.deepEqual(catalog.workspaces.map(row => row.remoteConnectionId), [undefined, 'a', 'b']); +}); + +const { remoteWorkspaceKey, remoteSessionBelongsToWorkspace, stampRemoteSessionWorkspace, + stampRemoteSessionDevice } = load('RemoteSessionIdentity'); + +test('session ownership distinguishes same paths on local and SSH hosts', () => { + const local = { path: '/project' }; + const a = { path: '/project', remoteConnectionId: 'a', remoteSshHost: 'host-a' }; + const b = { path: '/project', remoteConnectionId: 'b', remoteSshHost: 'host-b' }; + const session = stampRemoteSessionDevice(stampRemoteSessionWorkspace({ id: '1' }, a.path, + a.remoteConnectionId, a.remoteSshHost), 'desktop'); + for (const scope of [local, a, b]) { + assert.equal(remoteSessionBelongsToWorkspace(session, scope, [local, a, b]), scope === a); + } + assert.equal(session.deviceId, 'desktop'); + assert.equal(session.workspacePath, a.path); +}); + +test('legacy session paths never guess an SSH owner or an ambiguous catalog row', () => { + const session = { id: 'legacy', workspacePath: '/project' }; + const local = { path: '/project' }; + const ssh = { path: '/project', remoteConnectionId: 'ssh' }; + assert.equal(remoteSessionBelongsToWorkspace(session, local, [local]), true); + assert.equal(remoteSessionBelongsToWorkspace(session, local, [local, ssh]), false); + assert.equal(remoteSessionBelongsToWorkspace(session, ssh, [ssh]), false); + assert.equal(remoteSessionBelongsToWorkspace({ id: 'missing' }, local, [local]), false); +}); + +test('workspace identity normalizes trailing separators without aliasing missing paths to root', () => { + assert.equal(remoteWorkspaceKey({ path: '/project/' }), remoteWorkspaceKey({ path: '/project' })); + assert.notEqual(remoteWorkspaceKey({ path: '' }), remoteWorkspaceKey({ path: '/' })); + assert.notEqual(remoteWorkspaceKey({ path: '/project', remoteSshHost: 'a' }), + remoteWorkspaceKey({ path: '/project', remoteSshHost: 'b' })); +}); + +test('publishing an already scoped session preserves its owning SSH workspace', () => { + const original = stampRemoteSessionWorkspace({ id: 'a' }, '/project', 'ssh-a', 'host-a'); + assert.equal(stampRemoteSessionWorkspace(original, '/project', 'ssh-b', 'host-b'), original); + assert.equal(stampRemoteSessionWorkspace(original, '/local'), original); +}); + +const { RemoteCommandFactory } = load('RemoteCommandFactory'); +test('workspace list and creation commands preserve SSH scope without changing legacy omission', () => { + const listing = RemoteCommandFactory.listSessions('/repo', 50, 0, '', 'saved', 'host'); + assert.equal(listing.remote_connection_id, 'saved'); + assert.equal(listing.remote_ssh_host, 'host'); + const created = RemoteCommandFactory.createSession({ agentType: 'code', title: '', remoteConnectionId: 'saved', remoteSshHost: 'host' }, '/repo'); + assert.equal(created.remote_connection_id, 'saved'); + assert.equal(created.remote_ssh_host, 'host'); + const legacy = RemoteCommandFactory.listSessions('/repo', 50, 0, ''); + assert.equal(JSON.stringify(legacy).includes('remote_connection_id'), false); + assert.equal(JSON.stringify(legacy).includes('remote_ssh_host'), false); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/workspace-editor.test.cjs b/src/apps/mobile/harmonyos/tools/tests/workspace-editor.test.cjs index 0a809d50fd..ef2cf30ba1 100644 --- a/src/apps/mobile/harmonyos/tools/tests/workspace-editor.test.cjs +++ b/src/apps/mobile/harmonyos/tools/tests/workspace-editor.test.cjs @@ -1,7 +1,7 @@ const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),ts=require('typescript'); function fixture(){ const source=fs.readFileSync(path.join(__dirname,'../../entry/src/main/ets/pages/viewmodel/WorkspaceToolsViewModel.ets'),'utf8');const js=ts.transpileModule(source,{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.CommonJS}}).outputText;const exports={};new Function('require','exports',js)(name=>name.endsWith('WorkspaceFileUploadClient')?{WorkspaceFileUploadClient:class{hasPending(){return false;}}}:name.endsWith('Encoding')?{Encoding:{sha256:async value=>'hash:'+value}}:{},exports); - const state={visible:true,busy:false,dirty:false,editorVisible:false,editing:false,content:'',selectedFile:'',directory:'/project',entries:[],sort:0};const calls=[];const manager={async hostInvoke(command,args){calls.push({command,args});if(command==='read_file_content')return 'original';if(command==='terminal_list')return [];return {children:[],hasMore:false};}}; + const state={visible:true,busy:false,dirty:false,editorVisible:false,editing:false,content:'',selectedFile:'',directory:'/project',entries:[],sort:0};const calls=[];const manager={async hostInvoke(command,args){calls.push({command,args});if(command==='ssh_list_saved_connections')return [{id:'saved-other',name:'Other'}];if(command==='read_file_content')return 'original';if(command==='terminal_list')return [];return {children:[],hasMore:false};}}; const vm=new exports.WorkspaceToolsViewModel(state,manager,()=> 'runtime');vm.owner='runtime';vm.location={path:'/project',remoteConnectionId:'saved-ssh'};return {vm,state,calls}; } const tick=()=>new Promise(resolve=>setImmediate(resolve)); @@ -21,8 +21,9 @@ test('sidebar final entry hierarchy keeps modes in workspace plus and tools in t const sidebar=fs.readFileSync(path.join(components,'AppSidebar.ets'),'utf8'); assert.doesNotMatch(sidebar,/requestChat|sidebar\.newChat|sidebar\.conversations/); assert.match(sidebar,/workspaceTools\.title/); - assert.match(sidebar,/toolsPickerTargetId !== this.controlTargetDeviceId/); - assert.match(sidebar,/type: 'open-device', deviceId: this.toolsPickerTargetId/); + assert.match(sidebar,/if \(this.connectionState !== 'connected'\) return/); + assert.doesNotMatch(sidebar,/DeviceToolsPicker|showToolsPicker/); + assert.match(sidebar,/type: 'open-device', deviceId: this.controlTargetDeviceId/); for(const host of ['WideConversationHost.ets','AppRootOverlaySurfaces.ets']){ const source=fs.readFileSync(path.join(components,host),'utf8'); assert.match(source,/toolLocations: this.remotePageState.savedConnectionsTargetId/); @@ -30,15 +31,15 @@ test('sidebar final entry hierarchy keeps modes in workspace plus and tools in t } }); test('device tools local default comes from serving runtime home without reading or changing workspace',async()=>{ - const f=fixture();f.vm.manager.hostInvoke=async(command,args)=>{f.calls.push({command,args});if(command==='get_system_info')return {homeDir:'/runtime-home'};if(command==='terminal_list')return [{id:'other-cwd',initialCwd:'/elsewhere',connectionId:''},{id:'ssh',connectionId:'saved'}];return {children:[],hasMore:false};}; + const f=fixture();f.vm.manager.hostInvoke=async(command,args)=>{f.calls.push({command,args});if(command==='ssh_list_saved_connections')return [];if(command==='get_system_info')return {homeDir:'/runtime-home'};if(command==='terminal_list')return [{id:'other-cwd',initialCwd:'/elsewhere',connectionId:''},{id:'ssh',connectionId:'saved'}];return {children:[],hasMore:false};}; f.vm.dispatch({type:'open-device',deviceId:'runtime',connectionId:''});await tick(); assert.equal(f.state.directory,'/runtime-home');assert.deepEqual(f.state.terminals.map(x=>x.id),['other-cwd']); - assert.deepEqual(f.calls.map(x=>x.command),['get_system_info','get_directory_children_paginated','terminal_list']); - assert.equal(f.calls[1].args.request.remoteConnectionId,''); + assert.deepEqual(f.calls.map(x=>x.command),['ssh_list_saved_connections','get_system_info','get_directory_children_paginated','terminal_list']); + assert.equal(f.calls[2].args.request.remoteConnectionId,''); }); test('device tools saved SSH starts at POSIX root independently of runtime selected workspace',async()=>{ const f=fixture();f.vm.dispatch({type:'open-device',deviceId:'runtime',connectionId:'saved-other'});await tick(); - assert.equal(f.state.directory,'/');assert.equal(f.calls[0].command,'get_directory_children_paginated');assert.equal(f.calls[0].args.request.remoteConnectionId,'saved-other'); + assert.equal(f.state.directory,'/');assert.equal(f.calls[1].command,'get_directory_children_paginated');assert.equal(f.calls[1].args.request.remoteConnectionId,'saved-other'); f.vm.dispatch({type:'directory',path:'/etc'});await tick();assert.equal(f.vm.location.path,'/etc');assert.equal(f.vm.location.remoteConnectionId,'saved-other'); }); test('device parent navigation uses serving-runtime syntax for POSIX and Windows roots',()=>{ @@ -49,3 +50,79 @@ test('device parent navigation uses serving-runtime syntax for POSIX and Windows assert.equal(parent('C:\\Users\\name'),'C:/Users');assert.equal(parent('C:/'),'C:/'); assert.equal(parent('\\\\server\\share\\folder'),'//server/share'); }); + +test('device tools rejects an unavailable SSH identity instead of falling back to local files',async()=>{ + const f=fixture();f.vm.dispatch({type:'open-device',deviceId:'runtime',connectionId:'missing'});await tick(); + assert.match(f.state.error,/Saved connection is unavailable/); + assert.equal(f.calls.some(x=>x.command==='get_directory_children_paginated'),false); +}); +test('tools tab selection retains terminal output and never issues host commands',async()=>{ + const f=fixture();f.state.output='retained';f.state.terminalId='pty'; + f.vm.dispatch({type:'panel',tab:1});f.vm.dispatch({type:'panel',tab:0});await tick(); + assert.equal(f.state.initialTab,0);assert.equal(f.state.output,'retained');assert.equal(f.state.terminalId,'pty');assert.equal(f.calls.length,0); +}); + +test('list forms create relative files on the selected SSH host and close after refresh',async()=>{ + const f=fixture();f.vm.dispatch({type:'file-action',text:'file'});f.vm.dispatch({type:'file-action-name',text:'new.txt'}); + f.vm.dispatch({type:'file-action-submit'});await tick(); + assert.equal(f.calls[0].command,'write_file_content');assert.equal(f.calls[0].args.request.filePath,'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/project/new.txt'); + assert.equal(f.calls[0].args.request.remoteConnectionId,'saved-ssh');assert.equal(f.calls[0].args.request.expectedHash,''); + assert.equal(f.calls[1].command,'get_directory_children_paginated');assert.equal(f.state.fileAction,'');assert.equal(f.state.editorVisible,false); +}); +test('directory row deletion is non-recursive and does not need an editor selection',async()=>{ + const f=fixture();f.state.entries=[{path:'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/project/folder',name:'folder',isDirectory:true}]; + f.vm.dispatch({type:'file-action',text:'delete',path:'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/project/folder'});f.vm.dispatch({type:'file-action-submit'});await tick(); + assert.equal(f.calls[0].command,'delete_directory');assert.equal(f.calls[0].args.request.recursive,false);assert.equal(f.calls[0].args.request.path,'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/project/folder');assert.equal(f.state.fileAction,''); +}); +test('stale row keeps its form and error without sending a mutation',async()=>{ + const f=fixture();f.vm.dispatch({type:'file-action',text:'rename',path:'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/project/missing'});f.vm.dispatch({type:'file-action-name',text:'rename.txt'}); + f.vm.dispatch({type:'file-action-submit'});await tick();assert.equal(f.calls.length,0);assert.equal(f.state.fileAction,'rename'); + assert.equal(f.state.fileActionName,'rename.txt');assert.match(f.state.error,/no longer available/); +}); + +test('conflicting save keeps the draft and original hash through retry', async () => { + const f = fixture(); + f.vm.dispatch({ type: 'read', path: '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/project/a' }); await tick(); + f.vm.dispatch({ type: 'edit', text: 'unsaved draft' }); + const originalInvoke = f.vm.manager.hostInvoke; + let reject = true; + const writes = []; + f.vm.manager.hostInvoke = async (command, args) => { + if (command === 'write_file_content') { + writes.push(args.request); + if (reject) throw new Error('FILE_CONFLICT: File changed after it was read'); + } + return originalInvoke(command, args); + }; + f.vm.dispatch({ type: 'save' }); await tick(); + assert.equal(f.state.content, 'unsaved draft'); + assert.equal(f.state.dirty, true); + assert.equal(f.state.editorVisible, true); + assert.match(f.state.error, /FILE_CONFLICT:/); + reject = false; + f.vm.dispatch({ type: 'save' }); await tick(); + assert.deepEqual(writes.map(value => value.expectedHash), ['hash:original', 'hash:original']); + assert.equal(f.state.content, 'unsaved draft'); + assert.equal(f.state.dirty, false); + assert.equal(f.state.error, ''); +}); + +test('rejected provider fences a previous delayed directory response', async () => { + const f = fixture(); + const original = f.vm.manager.hostInvoke.bind(f.vm.manager); + let release; + f.vm.manager.hostInvoke = async (command, args) => { + if (command === 'get_directory_children_paginated') { + await new Promise(resolve => { release = resolve; }); + return { children: [{path:'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/previous/a',name:'a',isDirectory:false}], hasMore:false }; + } + return original(command, args); + }; + f.vm.dispatch({type:'directory',path:'/previous'}); await tick(); + f.vm.dispatch({type:'open-device',deviceId:'runtime',connectionId:'missing'}); await tick(); + assert.match(f.state.error,/Saved connection is unavailable/); + release(); await tick(); + assert.deepEqual(f.state.entries,[]); + assert.equal(f.state.directory,''); + assert.match(f.state.error,/Saved connection is unavailable/); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/workspace-refresh-race.test.cjs b/src/apps/mobile/harmonyos/tools/tests/workspace-refresh-race.test.cjs new file mode 100644 index 0000000000..afc5e8206a --- /dev/null +++ b/src/apps/mobile/harmonyos/tools/tests/workspace-refresh-race.test.cjs @@ -0,0 +1,51 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const ts = require('typescript'); +const source = fs.readFileSync(path.join(__dirname, '../../entry/src/main/ets/pages/viewmodel/RemoteWorkspaceViewModel.ets'), 'utf8'); +const js = ts.transpileModule(source, {compilerOptions: {target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS}}).outputText; +const exported = {}; +new Function('require', 'exports', js)(name => name.endsWith('RemoteLogger') ? {RemoteLogger: {info(){}, warn(){}}} : {}, exported); +function deferred() { + let resolve, reject; + const promise = new Promise((yes, no) => {resolve=yes; reject=no;}); + return {promise, resolve, reject}; +} +function fixture() { + let target = 'a'; + const requests=[], errors=[], catalogs=[]; + const state = {savedConnections:[{id:'existing'}], savedConnectionsTargetId:'a', setRecentWorkspaces(){}}; + const vm = new exported.RemoteWorkspaceViewModel(state, { + savedConnections(){const request=deferred(); requests.push(request); return request.promise;}, + async workspaceCatalog(){catalogs.push(target); return {workspaces:[], recentWorkspaces:[], source:'opened'};} + }, {remoteTargetId:()=>target, onCatalogLoading(){}, onCatalogLoaded(){}, onCatalogFailed(){}, onConnectionFailure:error=>errors.push(error)}); + return {vm,state,requests,errors,catalogs,target(value){target=value;}}; +} +test('refresh keeps current locations visible and an older response cannot replace the latest result', async()=>{ + const f=fixture(); const old=f.vm.loadRecentWorkspacesInBackground(); + assert.equal(f.state.savedConnections[0].id,'existing'); + const current=f.vm.loadRecentWorkspacesInBackground(); + f.requests[1].resolve([{id:'new'}]); await current; + f.requests[0].resolve([{id:'old'}]); await old; + assert.equal(f.state.savedConnections[0].id,'new'); + assert.deepEqual(f.catalogs,['a']); +}); +for(const outcome of ['success','failure']) test(`late ${outcome} from another device cannot mutate current state or continue its requests`, async()=>{ + const f=fixture(); const old=f.vm.loadRecentWorkspacesInBackground(); + f.target('b'); const current=f.vm.loadRecentWorkspacesInBackground(); + assert.deepEqual(f.state.savedConnections,[]); + f.requests[1].resolve([{id:'b-ssh'}]); await current; + if(outcome==='success') f.requests[0].resolve([{id:'a-ssh'}]); + else f.requests[0].reject(Error('Old device offline')); + await old; + assert.equal(f.state.savedConnectionsTargetId,'b'); + assert.equal(f.state.savedConnections[0].id,'b-ssh'); + assert.deepEqual(f.errors,[]); assert.deepEqual(f.catalogs,['b']); +}); +test('current connection failure remains visible and does not discard the saved list', async()=>{ + const f=fixture(); const pending=f.vm.loadRecentWorkspacesInBackground(); + f.requests[0].reject(Error('Unavailable')); await pending; + assert.equal(f.errors.length,1); assert.equal(f.state.savedConnections[0].id,'existing'); + assert.deepEqual(f.catalogs,['a']); +}); diff --git a/src/apps/mobile/harmonyos/tools/tests/workspace-upload.test.cjs b/src/apps/mobile/harmonyos/tools/tests/workspace-upload.test.cjs index 7b297316b0..eb8ceff297 100644 --- a/src/apps/mobile/harmonyos/tools/tests/workspace-upload.test.cjs +++ b/src/apps/mobile/harmonyos/tools/tests/workspace-upload.test.cjs @@ -4,7 +4,7 @@ const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto'); const ts = require('typescript'); -function fixture() { +function fixture(transform = (_request, status) => status) { const bytes = Buffer.alloc(2 * 1024 * 1024 + 17, 42); let closed = 0, offset = 0, lost = true, target = 'runtime-a'; const calls = [], reads = []; @@ -21,7 +21,7 @@ function fixture() { const manager = { async hostInvoke(command, {request}) { assert.equal(command, 'workspace_file_upload'); calls.push(structuredClone(request)); if (request.action === 'append') { assert.equal(request.offset, offset); offset += Buffer.from(request.contentBase64, 'base64').length; if (lost) { lost = false; throw new Error('ack lost'); } } - return { transferId: request.transferId, nextOffset: offset, totalBytes: bytes.length, completed: request.action === 'finish' }; + return transform(request, { transferId: request.transferId, nextOffset: offset, totalBytes: bytes.length, completed: request.action === 'finish' }); } }; return { client: new exported.WorkspaceFileUploadClient(manager, () => target), calls, reads, bytes, get closed() { return closed; }, setTarget(value) { target = value; } }; } @@ -49,3 +49,58 @@ test('runtime switch after an acknowledged chunk prevents the next transfer RPC' assert.equal(f.calls.filter(c=>c.action==='append').length,1); assert.equal(f.client.hasPending(),true); }); + +test('completed upload with short final cursor retains source for recovery', async () => { + const f = fixture((request, status) => request.action === 'finish' ? {...status, nextOffset: 0} : status); + await f.client.select('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/runtime/file', undefined, '', () => true, '/runtime'); + await assert.rejects(f.client.resume(() => true, () => {}), /complete/); + assert.equal(f.client.hasPending(), true); + assert.equal(f.closed, 0); + await f.client.cancel(); + assert.equal(f.closed, 1); +}); + +for (const field of ['transferId', 'totalBytes', 'nextOffset']) { + test(`upload rejects invalid ${field} on append and recovery acknowledgements`, async () => { + let invalid = true; + const f = fixture((request, status) => { + if (!invalid || !['append', 'status'].includes(request.action)) return status; + return {...status, [field]: field === 'transferId' ? 'another-transfer' : field === 'totalBytes' ? status.totalBytes + 1 : status.nextOffset + 0.5}; + }); + await f.client.select('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/runtime/file', undefined, '', () => true, '/runtime'); + await assert.rejects(f.client.resume(() => true, () => {}), /acknowledgement/); + assert.equal(f.client.hasPending(), true); + assert.equal(f.closed, 0); + assert.equal(f.calls.filter(c => c.action === 'append').length, 1); + invalid = false; + await f.client.resume(() => true, () => {}); + assert.equal(f.client.hasPending(), false); + assert.equal(f.closed, 1); + }); +} + +for (const phase of ['begin', 'finish']) { + test(`lost ${phase} acknowledgement queries state and completes without duplicate mutation`, async () => { + let completed = false; + let dropped = false; + const f = fixture((request, status) => { + if (request.action === 'finish') completed = true; + if (request.action === phase && !dropped) { dropped = true; throw new Error(`${phase} ack lost`); } + return {...status, completed}; + }); + await f.client.select('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/runtime/file', undefined, '', () => true, '/runtime'); + await f.client.resume(() => true, () => {}); + assert.equal(f.closed, 1); + assert.equal(f.client.hasPending(), false); + assert.equal(f.calls.filter(c => c.action === phase).length, 1); + assert.equal(f.calls.filter(c => c.action === 'append').length, 3); + assert.equal(f.calls.filter(c => c.action === 'status').length, 2); + }); +} +test('resuming an already completed upload closes source without another finish', async () => { + const f = fixture((_request, status) => ({...status, completed: true, nextOffset: status.totalBytes})); + await f.client.select('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/runtime/file', undefined, '', () => true, '/runtime'); + await f.client.resume(() => true, () => {}); + assert.equal(f.closed, 1); + assert.deepEqual(f.calls.map(c => c.action), ['begin']); +}); diff --git a/src/apps/mobile/ios/OpenBitFun/App/MobileLaunchConfiguration.swift b/src/apps/mobile/ios/OpenBitFun/App/MobileLaunchConfiguration.swift index fa82570b0b..7c6b179100 100644 --- a/src/apps/mobile/ios/OpenBitFun/App/MobileLaunchConfiguration.swift +++ b/src/apps/mobile/ios/OpenBitFun/App/MobileLaunchConfiguration.swift @@ -1,4 +1,6 @@ import Foundation +import OpenBitFunMobileCore +import OSLog @MainActor enum MobileLaunchConfiguration { @@ -21,7 +23,7 @@ enum MobileLaunchConfiguration { ChatMessage(id: UUID(), role: .user, text: "ไฝ ๅฅฝ"), ChatMessage(id: UUID(), role: .assistant, text: "่ฟ™ๆ˜ฏ OpenBitFun ็š„็งปๅŠจ็ซฏไผš่ฏ็•Œ้ขใ€‚ไฝ ๅฏไปฅไปŽๆ‰‹ๆœบ่ฟžๆŽฅๆกŒ้ข็ซฏ๏ผŒๆŸฅ็œ‹ๅทฅไฝœๅŒบใ€ไผš่ฏๅ’Œๆ™บ่ƒฝไฝ“็š„ๆ‰ง่กŒ็Šถๆ€ใ€‚") ], - connectCore: !streamingRegressionPreview && !ProcessInfo.processInfo.arguments.contains("--harness-preview") && designPreviewScenario() == nil + connectCore: !streamingRegressionPreview && !ProcessInfo.processInfo.arguments.contains("--permission-mailbox-preview") && !ProcessInfo.processInfo.arguments.contains("--harness-preview") && designPreviewScenario() == nil ) return configure(model) } @@ -40,6 +42,15 @@ enum MobileLaunchConfiguration { model.configureConnectedPreview() model.remoteHostCapabilities = ["harness_profiles_v1"] } + #if DEBUG + if arguments.contains("--permission-mailbox-preview") { + model.configureTimelinePreview() + model.permissionMailbox = PermissionMailboxUiState(requests: [ + PermissionMailboxRequest(requestId: "preview-write", action: "write", resources: ["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/workspace/readme.md"], toolCallId: nil, source: "Write") + ], busy: false, failed: false) + Logger(subsystem: "com.openbitfun.mobile.ios", category: "permission-mailbox").info("Mailbox preview requests=\(model.permissionMailbox?.requests.count ?? -1)") + } + #endif if arguments.contains("--connected") { model.configureConnectedPreview() } @@ -297,7 +308,7 @@ private extension MobileAppModel { MobileDeviceDirectoryEntry(id: "preview-mac", name: "Studio Mac", online: true, expanded: true, status: "FAILED", error: "REMOTE_UNAVAILABLE", workspaces: [failedWorkspace], sessions: [failedSession]), MobileDeviceDirectoryEntry(id: "preview-offline", name: "Office PC", online: false, expanded: false, status: "READY", error: nil, workspaces: [offlineWorkspace], sessions: [cachedSession]) ] - workspaceCatalog = [(path: "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/workspace/OpenBitFun", name: "OpenBitFun", selected: true, remoteConnectionId: nil)] + workspaceCatalog = [(path: "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/workspace/OpenBitFun", name: "OpenBitFun", selected: true, remoteConnectionId: nil, remoteSshHost: nil)] remoteAssistants = [ MobileAssistantOption(path: "/workspace/OpenBitFun/.openbitfun/assistants/review", name: "ไปฃ็ ๅฎกๆŸฅๅŠฉๆ‰‹") ] diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Chat/ChatTimelineView.swift b/src/apps/mobile/ios/OpenBitFun/Features/Chat/ChatTimelineView.swift index e9b8a4965e..f30a2c14ab 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Chat/ChatTimelineView.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Chat/ChatTimelineView.swift @@ -7,7 +7,6 @@ import UIKit struct ChatTimelineView: View { @ObservedObject var model: MobileAppModel var onLoadOlderMessages: (() -> Void)? = nil - @State private var expandedMailboxToolID: String? @StateObject private var scrollController = TimelineScrollController() @State private var historyAnchor: (id: String, top: CGFloat, firstID: String)? @@ -41,29 +40,19 @@ struct ChatTimelineView: View { } } label: { HStack(spacing: 7) { - if model.busy { ProgressView().controlSize(.small) } - Text(model.localized(model.busy ? "ๆญฃๅœจๅŠ ่ฝฝ" : "ๅŠ ่ฝฝๆ›ดๆ—ฉๆถˆๆฏ")) + if model.remoteHistoryLoading { ProgressView().controlSize(.small) } + Text(model.localized(model.remoteHistoryLoading ? "ๆญฃๅœจๅŠ ่ฝฝ" : (model.remoteHistoryFailed ? "Could not load earlier messages. Tap to retry." : "ๅŠ ่ฝฝๆ›ดๆ—ฉๆถˆๆฏ"))) .font(MobileDesignTypography.labelSmall.font) } .foregroundStyle(OpenBitFunTheme.muted) .frame(maxWidth: .infinity, minHeight: 38) } .buttonStyle(.plain) - .disabled(model.busy) + .disabled(model.busy || model.remoteHistoryLoading) .accessibilityIdentifier("timeline.loadOlder") } ForEach(historyRows) { row in timelineRow(row) } } - if model.surface == .remote, let mailbox = model.permissionMailbox { - if mailbox.failed { Text(model.localized("Permission request could not be completed. Retry to refresh pending requests.")); Button(model.localized("้‡่ฏ•")) { model.refreshPermissionMailbox() } } - ForEach(mailbox.questions, id: \.id) { question in - ToolStatusList(tools: [MobileAppModel.mapTool(question)], model: model, expandedToolID: $expandedMailboxToolID) - .simultaneousGesture(TapGesture().onEnded { model.startQuestionInteraction(question.id) }) - } - ForEach(mailbox.requests, id: \.requestId) { request in - PermissionMailboxRow(request: request, busy: mailbox.busy, model: model) - } - } // Keep the user's message and its reply in the same measured // tail, including acknowledgement and completion transitions. ForEach(currentTurnItems) { item in @@ -769,6 +758,7 @@ private struct MarkdownBlockView: View { .overlay(alignment: .leading) { Rectangle().fill(OpenBitFunTheme.line).frame(width: 2) } .textSelection(.enabled) case "list": + let markerWidth = listMarkerWidth VStack(alignment: .leading, spacing: 5) { ForEach(block.items, id: \.id) { item in HStack(alignment: .firstTextBaseline, spacing: 7) { @@ -786,7 +776,8 @@ private struct MarkdownBlockView: View { .padding(.horizontal, 2) } else { Text(item.marker).foregroundStyle(OpenBitFunTheme.muted) - .frame(width: 20, alignment: .trailing) + .fixedSize(horizontal: true, vertical: false) + .frame(width: markerWidth, alignment: .trailing) } Text(listItemInlineString(item)).foregroundStyle(OpenBitFunTheme.ink) .lineSpacing(MobileDesignTypography.bodyLarge.lineSpacing) @@ -824,6 +815,16 @@ private struct MarkdownBlockView: View { return nil } + private var listMarkerWidth: CGFloat { + let token = MobileDesignTypography.bodyLarge + let font = UIFontMetrics(forTextStyle: token.textStyle).scaledFont( + for: UIFont.systemFont(ofSize: token.size, weight: token.weight) + ) + return block.items.reduce(CGFloat(20)) { width, item in + max(width, ceil((item.marker as NSString).size(withAttributes: [.font: font]).width)) + } + } + private func listItemInlineString(_ item: MarkdownListItem) -> AttributedString { guard taskListState(item.text) != nil else { return markdownInlineString(item.inlines, fallback: item.text) @@ -1229,9 +1230,27 @@ private struct ToolStatusRow: View { @State private var selectedOptions: [Int: Set] = [:] @State private var otherAnswers: [Int: String] = [:] - private var emphasized: Bool { !tool.actions.isEmpty || expanded || tool.phase == "FAILED" } + private var transcriptActions: Set { + guard model.permissionMailbox?.ownsToolInteraction(toolId: tool.id) == true else { return tool.actions } + return tool.actions.subtracting(["ANSWER", "APPROVE", "REJECT"]) + .subtracting(tool.actions.contains("ANSWER") ? ["CANCEL"] : []) + } + + private var emphasized: Bool { !transcriptActions.isEmpty || expanded || tool.phase == "FAILED" } + + var mailboxQuestion = false + @ViewBuilder var body: some View { + if mailboxQuestion { + if tool.questions.isEmpty { legacyAnswerPanel } + else { structuredAnswerPanel } + } else { + toolRow + } + } + + private var toolRow: some View { VStack(alignment: .leading, spacing: 8) { Button { if !tool.input.isEmpty || !tool.output.isEmpty || !tool.filePath.isEmpty { @@ -1266,23 +1285,42 @@ private struct ToolStatusRow: View { if !tool.output.isEmpty { detailText(model.localized("่พ“ๅ‡บ"), tool.output) } } - if tool.actions.contains("ANSWER") { + if transcriptActions.contains("ANSWER") { if tool.questions.isEmpty { legacyAnswerPanel } else { structuredAnswerPanel } - } else if tool.actions.contains("APPROVE") || tool.actions.contains("REJECT") { - if tool.actions.contains("APPROVE") { - Button(model.localized("็ผ–่พ‘ๅŽๆ‰นๅ‡†")) { approvalInput = tool.input.isEmpty ? "{}" : tool.input; editingApproval.toggle() } - if editingApproval { TextEditor(text: $approvalInput).frame(minHeight: 120) } + } else if transcriptActions.contains("APPROVE") || transcriptActions.contains("REJECT") { + if transcriptActions.contains("APPROVE") { + HStack { + Spacer(minLength: 0) + Button(model.localized(editingApproval ? "ๆ”ถ่ตทๅ‚ๆ•ฐ" : "็ผ–่พ‘ๅ‚ๆ•ฐ")) { + if !editingApproval && approvalInput.isEmpty { approvalInput = tool.input.isEmpty ? "{}" : tool.input } + editingApproval.toggle() + } + .font(MobileDesignTypography.labelSmall.font).foregroundStyle(OpenBitFunTheme.muted) + .frame(minHeight: 32) + } + if editingApproval { + TextEditor(text: $approvalInput).font(.system(size: MobileDesignTypography.labelSmall.size, design: .monospaced)) + .frame(height: 96).scrollContentBackground(.hidden) + .padding(8).background(OpenBitFunTheme.card, in: RoundedRectangle(cornerRadius: 8)) + } } - HStack(spacing: 8) { - if tool.actions.contains("REJECT") { toolAction(model.localized("ๆ‹’็ป"), primary: false) { model.rejectTool(tool.id) } } - if tool.actions.contains("APPROVE") { toolAction(model.localized("ๅ…่ฎธ"), primary: true) { model.approveTool(tool.id, updatedInput: editingApproval ? approvalInput : nil) }.disabled(editingApproval && ((try? JSONSerialization.jsonObject(with: Data(approvalInput.utf8))) as? [String: Any]) == nil) } + HStack(spacing: MobileDesignGeometry.approvalCardGap) { + Spacer(minLength: 0) + if transcriptActions.contains("REJECT") { + compactApprovalAction(model.localized("ๆ‹’็ป"), primary: false) { model.rejectTool(tool.id) } + } + if transcriptActions.contains("APPROVE") { + compactApprovalAction(model.localized("ๆ‰นๅ‡†"), primary: true) { + model.approveTool(tool.id, updatedInput: editingApproval ? approvalInput : nil) + }.disabled(editingApproval && ((try? JSONSerialization.jsonObject(with: Data(approvalInput.utf8))) as? [String: Any]) == nil) + } } } - if tool.actions.contains("CANCEL") { + if transcriptActions.contains("CANCEL") { Button { model.cancelTool(tool.id) } label: { Text(model.localized("ๅœๆญขๆ‰ง่กŒ")) .font(MobileDesignTypography.labelMedium.font).foregroundStyle(OpenBitFunTheme.statusDanger) @@ -1297,6 +1335,16 @@ private struct ToolStatusRow: View { .overlay { if emphasized { RoundedRectangle(cornerRadius: 14).stroke(OpenBitFunTheme.line, lineWidth: 1) } } } + private func compactApprovalAction(_ title: String, primary: Bool, action: @escaping () -> Void) -> some View { + Button(action: action) { + Text(title).font(MobileDesignTypography.labelMedium.font).padding(.horizontal, 16) + .frame(height: MobileDesignGeometry.approvalActionHeight) + .foregroundStyle(primary ? OpenBitFunTheme.contentOnAction : OpenBitFunTheme.ink) + .background(primary ? MobileDesignColors.primaryAction : OpenBitFunTheme.card, + in: RoundedRectangle(cornerRadius: MobileDesignGeometry.approvalActionRadius)) + }.buttonStyle(.plain) + } + private var legacyAnswerPanel: some View { VStack(alignment: .leading, spacing: 8) { Text(tool.question ?? model.localized("่ฏท่พ“ๅ…ฅๅ›žๅค")).font(MobileDesignTypography.bodySmall.font) @@ -1305,7 +1353,7 @@ private struct ToolStatusRow: View { .font(MobileDesignTypography.bodyLarge.font).lineLimit(2...5).padding(10) .background(OpenBitFunTheme.card).clipShape(RoundedRectangle(cornerRadius: 11)) .overlay(RoundedRectangle(cornerRadius: 11).stroke(OpenBitFunTheme.line, lineWidth: 1)) - Button { model.answerTool(tool.id, answer: answer); answer = "" } label: { + Button { model.answerTool(tool.id, answer: answer) } label: { Text(model.localized("ๅ‘้€ๅ›žๅค")) .font(MobileDesignTypography.labelMedium.font).foregroundStyle(OpenBitFunTheme.contentOnAction) .frame(maxWidth: .infinity, minHeight: 40) @@ -1341,6 +1389,7 @@ private struct ToolStatusRow: View { .padding(.vertical, 5) } .buttonStyle(.plain) + .accessibilityIdentifier("question.option.\(tool.id).\(question.index).\(option.label)") .disabled(model.busy) if selected && isOther(option) { TextField(model.localized("่ฏท่พ“ๅ…ฅๅ›žๅค"), text: Binding( @@ -1365,7 +1414,7 @@ private struct ToolStatusRow: View { .background(structuredAnswersValid && !model.busy ? OpenBitFunTheme.accent : OpenBitFunTheme.muted) .clipShape(Capsule()) } - .buttonStyle(.plain).disabled(!structuredAnswersValid || model.busy) + .buttonStyle(.plain).accessibilityIdentifier("question.submit.\(tool.id)").disabled(!structuredAnswersValid || model.busy) } } @@ -1495,6 +1544,65 @@ private struct ToolStatusRow: View { } } +private struct PermissionMailboxMaxHeightKey: EnvironmentKey { + static let defaultValue: CGFloat = 280 +} + +extension EnvironmentValues { + var permissionMailboxMaxHeight: CGFloat { + get { self[PermissionMailboxMaxHeightKey.self] } + set { self[PermissionMailboxMaxHeightKey.self] = newValue } + } +} + +struct PermissionMailboxPanel: View { + @ObservedObject var model: MobileAppModel + @Environment(\.permissionMailboxMaxHeight) private var maxHeight + @State private var expandedMailboxToolID: String? + @State private var contentHeight: CGFloat = 1 + var body: some View { + if model.surface == .remote, let mailbox = model.permissionMailbox, + mailbox.failed || !mailbox.requests.isEmpty || !mailbox.questions.isEmpty { + ScrollView { + VStack(alignment: .leading, spacing: MobileDesignGeometry.approvalCardGap) { + if mailbox.failed { + HStack { + Text(model.localized("Permission request could not be completed. Retry to refresh pending requests.")) + .foregroundStyle(OpenBitFunTheme.statusDanger) + Button(model.localized("้‡่ฏ•")) { model.refreshPermissionMailbox() }.disabled(mailbox.busy) + }.font(MobileDesignTypography.bodySmall.font) + } + ForEach(mailbox.questions, id: \.id) { question in + ToolStatusRow(tool: MobileAppModel.mapTool(question), model: model, expandedToolID: $expandedMailboxToolID, mailboxQuestion: true) + .disabled(mailbox.busy) + .simultaneousGesture(TapGesture().onEnded { model.startQuestionInteraction(question.id) }) + } + ForEach(mailbox.requests, id: \.requestId) { request in + PermissionMailboxRow(request: request, busy: mailbox.busy, model: model) + } + } + .padding(.horizontal, MobileDesignGeometry.contentGutter).padding(.vertical, 8) + .background(GeometryReader { proxy in + Color.clear.preference(key: PermissionMailboxHeightKey.self, value: proxy.size.height) + }) + } + .frame(height: min(contentHeight, maxHeight)) + .onPreferenceChange(PermissionMailboxHeightKey.self) { height in + contentHeight = height + #if DEBUG + Logger(subsystem: "com.openbitfun.mobile.ios", category: "permission-mailbox").info("Mailbox layout height=\(height) limit=\(maxHeight) requests=\(mailbox.requests.count)") + #endif + } + .id(model.selectedSessionID) + } + } +} + +private struct PermissionMailboxHeightKey: PreferenceKey { + static let defaultValue: CGFloat = 1 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) } +} + private struct PermissionMailboxRow: View { let request: PermissionMailboxRequest let busy: Bool @@ -1507,16 +1615,52 @@ private struct PermissionMailboxRow: View { return object is [String: Any] } var body: some View { - VStack(alignment: .leading, spacing: 8) { - Text(request.source.isEmpty ? request.action : request.source) - Text(request.action) - Text(request.resources.joined(separator: "\n")) - if editing { TextEditor(text: $input).frame(minHeight: 90).disabled(busy) } - HStack { - Button(model.localized("็ผ–่พ‘ๅŽๆ‰นๅ‡†")) { editing.toggle() }.disabled(busy) - Button(model.localized("ๆ‰นๅ‡†")) { model.respondPermission(request.requestId, approve: true, updatedInput: editing ? input : nil) }.disabled(busy || !valid) - Button(model.localized("ๆ‹’็ป")) { model.respondPermission(request.requestId, approve: false, updatedInput: nil) }.disabled(busy) + VStack(alignment: .leading, spacing: MobileDesignGeometry.approvalCardGap) { + HStack(spacing: 8) { + Image("ApprovalShield").resizable().frame(width: 16, height: 16) + Text(request.source.isEmpty ? request.action : request.source) + .font(MobileDesignTypography.bodySmall.font).foregroundStyle(OpenBitFunTheme.ink) + Spacer(minLength: 8) + Button(model.localized(editing ? "ๆ”ถ่ตทๅ‚ๆ•ฐ" : "็ผ–่พ‘ๅ‚ๆ•ฐ")) { editing.toggle() } + .font(MobileDesignTypography.labelSmall.font).foregroundStyle(OpenBitFunTheme.muted) + .frame(minHeight: 32).disabled(busy) + } + if !request.source.isEmpty && request.source.lowercased() != request.action.lowercased() { + Text(request.action).font(MobileDesignTypography.bodySmall.font).foregroundStyle(OpenBitFunTheme.muted) + } + if !request.resources.isEmpty { + Text(request.resources.joined(separator: "\n")) + .font(.system(size: MobileDesignTypography.labelSmall.size, design: .monospaced)) + .foregroundStyle(OpenBitFunTheme.ink).frame(maxWidth: .infinity, alignment: .leading) + .padding(10).background(OpenBitFunTheme.soft, in: RoundedRectangle(cornerRadius: 8)) + .textSelection(.enabled) } - }.padding(12) + if editing { + TextEditor(text: $input).font(.system(size: MobileDesignTypography.labelSmall.size, design: .monospaced)) + .frame(height: 96).disabled(busy).scrollContentBackground(.hidden) + .padding(8).background(OpenBitFunTheme.soft, in: RoundedRectangle(cornerRadius: 8)) + .overlay(RoundedRectangle(cornerRadius: 8).stroke(valid ? OpenBitFunTheme.line : OpenBitFunTheme.statusDanger, lineWidth: 1)) + } + HStack(spacing: MobileDesignGeometry.approvalCardGap) { + Spacer(minLength: 0) + Button { model.respondPermission(request.requestId, approve: false, updatedInput: nil) } label: { + Text(model.localized("ๆ‹’็ป")).padding(.horizontal, 16) + .frame(height: MobileDesignGeometry.approvalActionHeight) + .foregroundStyle(OpenBitFunTheme.ink) + .background(OpenBitFunTheme.soft, in: RoundedRectangle(cornerRadius: MobileDesignGeometry.approvalActionRadius)) + }.disabled(busy) + .accessibilityIdentifier("permission.reject.\(request.requestId)") + Button { model.respondPermission(request.requestId, approve: true, updatedInput: editing ? input : nil) } label: { + Text(model.localized("ๆ‰นๅ‡†")).padding(.horizontal, 16) + .frame(height: MobileDesignGeometry.approvalActionHeight) + .foregroundStyle(OpenBitFunTheme.contentOnAction) + .background(MobileDesignColors.primaryAction, in: RoundedRectangle(cornerRadius: MobileDesignGeometry.approvalActionRadius)) + }.disabled(busy || !valid).opacity(busy || !valid ? 0.5 : 1) + .accessibilityIdentifier("permission.approve.\(request.requestId)") + }.font(MobileDesignTypography.labelMedium.font) + } + .buttonStyle(.plain).padding(MobileDesignGeometry.approvalCardPadding) + .background(OpenBitFunTheme.card, in: RoundedRectangle(cornerRadius: MobileDesignGeometry.approvalCardRadius)) + .overlay(RoundedRectangle(cornerRadius: MobileDesignGeometry.approvalCardRadius).stroke(OpenBitFunTheme.line, lineWidth: 1)) } } diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Chat/ConversationHeader.swift b/src/apps/mobile/ios/OpenBitFun/Features/Chat/ConversationHeader.swift index 1330a32512..6ca3118845 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Chat/ConversationHeader.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Chat/ConversationHeader.swift @@ -13,7 +13,6 @@ struct ConversationHeader: View { private var resolvedTitle: String { if let title = model.selectedSession?.title, !title.isEmpty { return title } - if model.surface == .remote { return model.localized("่ฟœ็จ‹") } return "OpenBitFun" } diff --git a/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift b/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift index f6b3dd516c..0a59abdbcb 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift @@ -70,6 +70,7 @@ enum MobileDesignColors { static let welcomeButton = dynamic(light: 0xFFFFFFFF, dark: 0xFFFFFFFF) static let welcomeButtonLabel = dynamic(light: 0xFF171917, dark: 0xFF171917) static let welcomeSecondary = dynamic(light: 0xFFB9BCB9, dark: 0xFFB9BCB9) + static let statusWarning = dynamic(light: 0xFFFF8C00, dark: 0xFFFF8C00) private static func dynamic(light: UInt32, dark: UInt32) -> Color { Color(uiColor: UIColor { traits in @@ -183,6 +184,11 @@ enum MobileDesignGeometry { static let recentHomeTitleSize: CGFloat = 25 static let recentHomeMaxWidth: CGFloat = 560 static let recentHomeRowPadding: CGFloat = 18 + static let approvalCardRadius: CGFloat = 12 + static let approvalCardPadding: CGFloat = 12 + static let approvalCardGap: CGFloat = 8 + static let approvalActionRadius: CGFloat = 8 + static let approvalActionHeight: CGFloat = 36 } enum MobileDesignBreakpoints { diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Remote/RemoteHomeViews.swift b/src/apps/mobile/ios/OpenBitFun/Features/Remote/RemoteHomeViews.swift index 11e67fa028..08e64dfea4 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Remote/RemoteHomeViews.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Remote/RemoteHomeViews.swift @@ -6,13 +6,13 @@ struct RemoteConnectedHomeView: View { var onBrowse: (() -> Void)? private var recent: [ChatSession] { - Array(model.remoteSessions.filter { $0.status != "archived" }.sorted { - timestamp($0) > timestamp($1) - }.prefix(3)) - } - private func timestamp(_ session: ChatSession) -> Int64 { - let updated = SessionTimePresentation.shared.timestampMs(value: session.updatedLabel)?.int64Value ?? 0 - return updated > 0 ? updated : SessionTimePresentation.shared.timestampMs(value: session.createdAt)?.int64Value ?? 0 + let candidates = model.remoteSessions.map { + RecentSessionUiState(id: $0.id, status: $0.status, + updatedAt: $0.updatedLabel, createdAt: $0.createdAt) + } + return RecentSessionsPresentation.shared.sessionIds(sessions: candidates).compactMap { id in + model.remoteSessions.first { $0.id == id } + } } private func context(_ session: ChatSession) -> String { let device = model.accountDevices.first { $0.id == session.deviceKey }?.name ?? model.accountDeviceName ?? "" @@ -81,6 +81,7 @@ struct RemoteConnectedHomeView: View { Text(model.localized("ๆœ€่ฟ‘ไผš่ฏ")) Spacer() Button(model.localized("ๅ…จ้ƒจไผš่ฏ"), action: browse) + .foregroundStyle(OpenBitFunTheme.ink) }.font(.system(size: 12)).foregroundStyle(OpenBitFunTheme.muted) } ForEach(recent) { session in @@ -95,8 +96,6 @@ struct RemoteConnectedHomeView: View { .foregroundStyle(OpenBitFunTheme.muted).lineLimit(1) } Spacer(minLength: 0) - Image(systemName: "chevron.right").font(.system(size: 12)) - .foregroundStyle(OpenBitFunTheme.muted) }.padding(.vertical, MobileDesignGeometry.recentHomeRowPadding) .contentShape(Rectangle()) }.buttonStyle(.plain) diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Shell/MobileShellView.swift b/src/apps/mobile/ios/OpenBitFun/Features/Shell/MobileShellView.swift index 9ef4804c74..379b65fbd1 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Shell/MobileShellView.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Shell/MobileShellView.swift @@ -11,6 +11,7 @@ struct MobileShellView: View { var body: some View { GeometryReader { proxy in adaptiveSurface(viewportWidth: proxy.size.width, viewportHeight: proxy.size.height) + .environment(\.permissionMailboxMaxHeight, proxy.size.height * 0.4) } .overlayPreferenceValue(SessionActionsAnchorKey.self) { anchor in GeometryReader { proxy in @@ -92,14 +93,7 @@ struct MobileShellView: View { } } .animation(.easeOut(duration: 0.18), value: model.toastMessage) - .sheet(isPresented: $model.downloadExporterOpen) { - if let download = model.pendingDownload { - RuntimeDownloadExporter(url: download.localURL, name: download.name) { saved in - guard model.pendingDownload?.localURL == download.localURL else { return } - model.finishDownloadExport(success: saved) - } - } - } + .modifier(RuntimeDownloadPresentation(model: model, enabled: model.runtimeDeviceTools?.visible != true && model.filePreview == nil)) } @@ -331,18 +325,12 @@ struct MobileShellView: View { sidebarActionLabel: sidebarActionLabel ) } - if model.surface == .remote, - model.remoteExpectedDeviceKey != nil, - model.connectionPhase != .connected, - !model.remoteConversationLoading, - model.remoteSessionSelected || model.connectionPhase == .reconnecting || model.coreErrorMessage != nil { - RemoteConversationStatusBar(model: model) - } if showsWelcomeHome { WelcomeHomeView(model: model) } else if model.surface == .remote && !model.remoteSessionSelected { RemoteConnectedHomeView(model: model, onBrowse: sidebarAction) } else { + PermissionMailboxPanel(model: model) ZStack { ChatTimelineView(model: model) if model.surface == .remote && model.remoteConversationLoading { @@ -375,6 +363,24 @@ struct MobileShellView: View { } +struct RuntimeDownloadPresentation: ViewModifier { + @ObservedObject var model: MobileAppModel + let enabled: Bool + func body(content: Content) -> some View { + content.sheet(isPresented: Binding( + get: { enabled && model.downloadExporterOpen }, + set: { if enabled { model.downloadExporterOpen = $0 } } + )) { + if let download = model.pendingDownload { + RuntimeDownloadExporter(url: download.localURL, name: download.name) { saved in + guard model.pendingDownload?.localURL == download.localURL else { return } + model.finishDownloadExport(success: saved) + } + } + } + } +} + private struct RuntimeDownloadExporter: UIViewControllerRepresentable { let url: URL let name: String diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteCreateSessionView.swift b/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteCreateSessionView.swift index fa5eed18cf..fc7cc35d20 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteCreateSessionView.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteCreateSessionView.swift @@ -485,7 +485,7 @@ struct RemoteCreateSessionView: View { if let state = model.runtimeDirectoryPicker { Text(state.directory).font(.caption).padding() if state.busy { ProgressView() } - if state.failed { Text(model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")) } + if state.failed { Text(state.errorDetail ?? model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")) } List { Button(model.localized("Parent folder")) { model.browseRuntimeDirectories((state.directory as NSString).deletingLastPathComponent.isEmpty ? "/" : (state.directory as NSString).deletingLastPathComponent, connectionId: directoryConnectionId) }.disabled(state.directory == "/" || state.busy) ForEach(state.entries.filter { $0.directory }, id: \.path) { entry in @@ -665,6 +665,7 @@ struct HarnessProfileLabel: View { private struct NativeRuntimeTerminalView: UIViewRepresentable { + @Environment(\.colorScheme) private var colorScheme let state: RuntimeTerminalUiState let onInput: (String) -> Void let onResize: (Int, Int) -> Void @@ -672,6 +673,20 @@ private struct NativeRuntimeTerminalView: UIViewRepresentable { func makeUIView(context: Context) -> WKWebView { let configuration = WKWebViewConfiguration() configuration.userContentController.add(context.coordinator, name: "openbitfunTerminal") + #if DEBUG + configuration.userContentController.addUserScript(WKUserScript(source: """ + for (const name of ['touchend', 'click', 'focusin', 'focusout']) { + document.addEventListener(name, event => { + const trusted = event.isTrusted; + setTimeout(() => window.webkit.messageHandlers.openbitfunTerminal.postMessage({ + type: 'focus-diagnostic', event: name, trusted, + documentFocused: document.hasFocus(), + inputFocused: document.activeElement?.classList.contains('xterm-helper-textarea') === true + }), 0); + }, true); + } + """, injectionTime: .atDocumentEnd, forMainFrameOnly: true)) + #endif let view = WKWebView(frame: .zero, configuration: configuration) view.navigationDelegate = context.coordinator view.scrollView.isScrollEnabled = false @@ -694,8 +709,26 @@ private struct NativeRuntimeTerminalView: UIViewRepresentable { var ready = false var epoch: String? var revision: Int64 = -1 + private var appliedTheme: String? init(_ parent: NativeRuntimeTerminalView) { self.parent = parent } + private func renderTheme() { + guard ready, !disposed, let view else { return } + let traits = UITraitCollection(userInterfaceStyle: parent.colorScheme == .dark ? .dark : .light) + func css(_ color: Color) -> String { + var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 + UIColor(color).resolvedColor(with: traits).getRed(&red, green: &green, blue: &blue, alpha: &alpha) + return String(format: "#%02X%02X%02X", Int((red * 255).rounded()), Int((green * 255).rounded()), Int((blue * 255).rounded())) + } + let theme = ["background": css(OpenBitFunTheme.page), "foreground": css(OpenBitFunTheme.ink), "cursor": css(OpenBitFunTheme.ink)] + guard let data = try? JSONSerialization.data(withJSONObject: theme, options: .sortedKeys), + let json = String(data: data, encoding: .utf8), json != appliedTheme else { return } + view.isOpaque = false + view.backgroundColor = UIColor(OpenBitFunTheme.page).resolvedColor(with: traits) + view.evaluateJavaScript("window.OpenBitFunTerminal.setTheme(\(json))") + appliedTheme = json + } func render(force: Bool) { + renderTheme() let state = parent.state guard ready, !disposed, let id = state.sessionId else { return } guard force || epoch != id || revision != state.revision else { return } @@ -708,6 +741,17 @@ private struct NativeRuntimeTerminalView: UIViewRepresentable { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard !disposed, let event = message.body as? [String: Any], let type = event["type"] as? String else { return } switch type { + #if DEBUG + case "focus-diagnostic": + let log = Logger(subsystem: "com.openbitfun.mobile.ios", category: "terminal-focus") + let name = event["event"] as? String ?? "unknown" + let trusted = event["trusted"] as? Bool ?? false + let documentFocused = event["documentFocused"] as? Bool ?? false + let inputFocused = event["inputFocused"] as? Bool ?? false + let keyWindow = view?.window?.isKeyWindow ?? false + let responder = view.map { Self.responderName($0) } ?? "detached" + log.info("Terminal focus event=\(name, privacy: .public) trusted=\(trusted) document=\(documentFocused) input=\(inputFocused) keyWindow=\(keyWindow) responder=\(responder, privacy: .public)") + #endif case "ready": ready = true; render(force: true) case "resync": render(force: true) case "input": if let data = event["data"] as? String { parent.onInput(data) } @@ -715,6 +759,16 @@ private struct NativeRuntimeTerminalView: UIViewRepresentable { default: break } } + #if DEBUG + private static func responderName(_ view: UIView) -> String { + if view.isFirstResponder { return String(describing: type(of: view)) } + for child in view.subviews { + let name = responderName(child) + if name != "none" { return name } + } + return "none" + } + #endif func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.evaluateJavaScript("window.OpenBitFunTerminal.connect()") } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { decisionHandler(navigationAction.request.url?.isFileURL == true ? .allow : .cancel) @@ -724,19 +778,24 @@ private struct NativeRuntimeTerminalView: UIViewRepresentable { private struct NativeRuntimeFileEditor: View { @ObservedObject var model: MobileAppModel - @State private var content = "" + @ObservedObject private var draft: RuntimeFileDraftState + private var content: String { draft.content } @State private var discard = false @State private var rename = false @State private var delete = false @State private var renamePath = "" private var dirty: Bool { content != (model.runtimeFiles?.content ?? "") } + init(model: MobileAppModel) { + self.model = model + self.draft = model.runtimeFileDraft + } var body: some View { NavigationStack { VStack(spacing: 0) { if let files = model.runtimeFiles { Text(files.file ?? "").font(.caption).frame(maxWidth: .infinity, alignment: .leading).padding(12) - if files.failed { Text(model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")) } - NativeNumberedCodeEditor(text: $content, enabled: !files.busy).frame(maxWidth: .infinity, maxHeight: .infinity) + if files.failed { Text(files.saveConflict ? model.localized("File changed on the computer. Your draft is preserved. Copy it before reopening the file to load the latest version.") : files.errorDetail ?? model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")) } + NativeNumberedCodeEditor(text: $draft.content, enabled: !files.busy).frame(maxWidth: .infinity, maxHeight: .infinity) } } .navigationTitle(((model.runtimeFiles?.file ?? "") as NSString).lastPathComponent) @@ -751,8 +810,6 @@ private struct NativeRuntimeFileEditor: View { } ToolbarItem(placement: .confirmationAction) { Button(model.localized("ไฟๅญ˜ๆ–‡ไปถ")) { model.saveRuntimeFile(content) }.disabled(!dirty || model.runtimeFiles?.busy == true) } } - .onAppear { content = model.runtimeFiles?.content ?? "" } - .onChange(of: model.runtimeFiles?.content) { content = $0 ?? "" } .alert(model.localized("้‡ๅ‘ฝๅๆ‰“ๅผ€็š„ๆ–‡ไปถ"), isPresented: $rename) { TextField(model.localized("ๆ–‡ไปถ่ทฏๅพ„"), text: $renamePath).autocorrectionDisabled().textInputAutocapitalization(.never) Button(model.localized("้‡ๅ‘ฝๅๆ‰“ๅผ€็š„ๆ–‡ไปถ")) { model.renameRuntimeFile(renamePath) }.disabled(renamePath.isEmpty) @@ -850,72 +907,232 @@ private final class NumberedCodeEditorView: UIView, UITextViewDelegate { struct NativeDeviceToolsView: View { @ObservedObject var model: MobileAppModel - let terminal: Bool + private var terminal: Bool { model.runtimeDeviceTools?.panel == .terminal } let rootPath: String let deviceKey: String? let onBack: () -> Void - @State private var filePath = "" - @State private var uploadPicker = false + private func panelTab(_ title: String, isTerminal: Bool) -> some View { + let selected = terminal == isTerminal + return Button { model.selectDeviceToolsPanel(terminal: isTerminal) } label: { + VStack(spacing: 0) { + Text(title) + .fontWeight(selected ? .semibold : .regular) + .frame(maxWidth: .infinity, minHeight: 44) + Rectangle().fill(OpenBitFunTheme.ink) + .frame(height: 2).opacity(selected ? 1 : 0) + } + .foregroundStyle(selected ? OpenBitFunTheme.ink : OpenBitFunTheme.muted) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .accessibilityIdentifier(isTerminal ? "device.tools.panel.terminal" : "device.tools.panel.files") + .accessibilityAddTraits(selected ? .isSelected : []) + } var body: some View { NavigationStack { VStack(spacing: 0) { - if terminal { + Menu { + Button(model.localized("ๅ—ๆŽง่ฎพๅค‡ๆœฌๆœบ")) { model.openDeviceTools() } + ForEach(model.savedRuntimeConnections, id: \.id) { connection in + Button(connection.name) { model.openDeviceTools(connectionId: connection.id) } + } + } label: { + HStack { + Image(systemName: "desktopcomputer") + Text(model.savedRuntimeConnections.first(where: { $0.id == model.runtimeDeviceTools?.connectionId })?.name ?? model.localized("ๅ—ๆŽง่ฎพๅค‡ๆœฌๆœบ")) + Image(systemName: "chevron.down") + }.frame(maxWidth: .infinity, alignment: .leading).padding() + }.disabled(model.runtimeDeviceTools?.busy == true || model.runtimeFiles?.file != nil) + HStack(spacing: 0) { + panelTab(model.localized("ๆต่งˆๆ–‡ไปถ"), isTerminal: false) + panelTab(model.localized("็ปˆ็ซฏ"), isTerminal: true) + }.disabled(model.runtimeFiles?.file != nil) + if model.runtimeDeviceTools?.busy == true { + ProgressView().frame(maxWidth: .infinity, maxHeight: .infinity) + } else if model.runtimeDeviceTools?.failed == true { + Button(model.localized("้‡่ฏ•")) { model.openDeviceTools(connectionId: model.runtimeDeviceTools?.connectionId) } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else if terminal { if let state = model.runtimeTerminal { - if state.failed { Text(model.localized("็ปˆ็ซฏ่ฏทๆฑ‚ๅคฑ่ดฅใ€‚")) } + if state.failed { Text(state.errorDetail ?? model.localized("็ปˆ็ซฏ่ฏทๆฑ‚ๅคฑ่ดฅใ€‚")) } if state.busy { ProgressView() } if state.sessionId != nil { NativeRuntimeTerminalView(state: state, onInput: model.writeRuntimeTerminal, onResize: model.resizeRuntimeTerminal).frame(maxWidth: .infinity, maxHeight: .infinity) } + else if !state.busy { + VStack(spacing: 20) { + Image(systemName: "terminal") + .font(.system(size: 36)) + .foregroundStyle(OpenBitFunTheme.muted) + Button(model.localized("ๆ‰“ๅผ€็ปˆ็ซฏ")) { model.startDeviceToolsTerminal() } + .accessibilityIdentifier("device.tools.openTerminal") + .buttonStyle(.bordered) + .controlSize(.large) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(16) + } } } else { - ScrollView { - VStack { - TextField(model.localized("ๆ–‡ไปถ่ทฏๅพ„"), text: $filePath).autocorrectionDisabled().textInputAutocapitalization(.never) - Button(model.localized("ๆต่งˆๆ–‡ไปถ")) { model.browseRuntimeFiles(filePath) } - if let files = model.runtimeFiles { - Menu(model.localized("Sort files")) { - Button(model.localized("Name: Aโ€“Z")) { model.sortRuntimeFiles(.nameAsc) } - Button(model.localized("Name: Zโ€“A")) { model.sortRuntimeFiles(.nameDesc) } - Button(model.localized("Modified: newest first")) { model.sortRuntimeFiles(.modifiedDesc) } - Button(model.localized("Modified: oldest first")) { model.sortRuntimeFiles(.modifiedAsc) } - } - Button(model.localized("Parent folder")) { model.browseRuntimeFiles((files.directory as NSString).deletingLastPathComponent.isEmpty ? "/" : (files.directory as NSString).deletingLastPathComponent) }.disabled(files.directory == "/") - ForEach(files.entries, id: \.path) { entry in - Button(entry.name) { - if entry.directory { model.browseRuntimeFiles(entry.path) } - else { model.readRuntimeFile(entry.path) } - } - if !entry.directory { - Button(model.localized("ไธ‹่ฝฝ")) { model.downloadWorkspaceFile(path: entry.path, label: entry.name) } - } - } - if files.hasMore { Button(model.localized("ๆ˜พ็คบๆ›ดๅคš")) { model.browseRuntimeFiles(files.directory, append: true) } } - if files.failed { Text(model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")) } - } - Button(model.localized("Upload file")) { uploadPicker = true }.disabled(filePath.isEmpty) - .fileImporter(isPresented: $uploadPicker, allowedContentTypes: [.data], allowsMultipleSelection: false) { result in - guard model.remoteExpectedDeviceKey == deviceKey else { return } - switch result { - case .success(let urls): if let url = urls.first { model.uploadRuntimeFile(filePath, url: url) } - case .failure: model.showToast(model.localized("Could not read the selected file. Choose a local file and retry.")) - } - } - Button(model.localized("ๆ–ฐๅปบๆ–‡ไปถ")) { model.createRuntimeFile(filePath) }.disabled(filePath.isEmpty) - if model.runtimeFiles?.file != nil { - Button(model.localized("้‡ๅ‘ฝๅๆ‰“ๅผ€็š„ๆ–‡ไปถ")) { model.renameRuntimeFile(filePath) }.disabled(filePath.isEmpty) - Button(model.localized("ๅˆ ้™คๆ‰“ๅผ€็š„ๆ–‡ไปถ")) { model.deleteRuntimeFile() } - } - Button(model.localized("ๅˆ›ๅปบๆ–‡ไปถๅคน")) { model.createRuntimeDirectory(filePath) } - }.padding().disabled(model.runtimeFiles?.busy == true) - } + NativeRuntimeFileBrowser(model: model, deviceKey: deviceKey) + } - }.navigationTitle(model.localized(terminal ? "ๆ‰“ๅผ€็ปˆ็ซฏ" : "ๆต่งˆๆ–‡ไปถ")) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .navigationBarTitleDisplayMode(.inline) + .tint(OpenBitFunTheme.ink) + .navigationTitle(model.localized("Device tools")) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(model.localized("่ฟ”ๅ›ž"), action: onBack) } - ToolbarItem(placement: .confirmationAction) { if terminal { Button(model.localized("ๅ…ณ้—ญ็ปˆ็ซฏ")) { model.closeRuntimeTerminal(); onBack() } } } + ToolbarItemGroup(placement: .confirmationAction) { + if terminal, let state = model.runtimeTerminal, state.sessionId != nil { + Button(model.localized("ๅœๆญข")) { model.writeRuntimeTerminal("\u{03}") } + .disabled(state.busy) + Button(model.localized("ๅ…ณ้—ญ็ปˆ็ซฏ")) { model.closeRuntimeTerminal() } + .disabled(state.busy) + } + } } - .onAppear { filePath = model.runtimeFiles?.directory ?? rootPath } - .onChange(of: model.runtimeFiles?.directory) { if !terminal, let directory = $0 { filePath = directory } } .onChange(of: model.remoteExpectedDeviceKey) { if $0 != deviceKey { onBack() } } .fullScreenCover(isPresented: Binding(get: { !terminal && model.runtimeFiles?.file != nil }, set: { if !$0 { model.closeRuntimeFileEditor() } })) { NativeRuntimeFileEditor(model: model) } } + .modifier(RuntimeDownloadPresentation(model: model, enabled: model.runtimeDeviceTools?.visible == true)) + } +} + +private enum RuntimeFileListAction: String, Identifiable { + case file, directory, rename, delete, upload + var id: String { rawValue } + var label: String { + switch self { + case .file: return "ๆ–ฐๅปบๆ–‡ไปถ" + case .directory: return "ๅˆ›ๅปบๆ–‡ไปถๅคน" + case .rename: return "้‡ๅ‘ฝๅ" + case .delete: return "ๅˆ ้™ค" + case .upload: return "Upload file" + } + } +} + +private struct NativeRuntimeFileBrowser: View { + @ObservedObject var model: MobileAppModel + let deviceKey: String? + @State private var action: RuntimeFileListAction? + @State private var name = "" + @State private var target = "" + @State private var submittedRevision: Int64? + @State private var uploadPicker = false + private var busy: Bool { model.runtimeFiles?.busy == true } + private func begin(_ value: RuntimeFileListAction, path: String = "", name: String = "") { + self.action = value; self.target = path; self.name = name; submittedRevision = nil + } + var body: some View { + VStack(spacing: 0) { + if let files = model.runtimeFiles { + VStack(alignment: .leading, spacing: 8) { + Text(files.directory).font(.caption).foregroundStyle(OpenBitFunTheme.muted).lineLimit(2) + HStack { + Button { model.browseRuntimeFiles((files.directory as NSString).deletingLastPathComponent.isEmpty ? "/" : (files.directory as NSString).deletingLastPathComponent) } label: { Image(systemName: "arrow.up").frame(width: 44, height: 44) }.accessibilityLabel(model.localized("Parent folder")).disabled(files.directory == "/") + Button { model.browseRuntimeFiles(files.directory) } label: { Image(systemName: "arrow.clockwise").frame(width: 44, height: 44) }.accessibilityLabel(model.localized("ๅˆทๆ–ฐ")) + Menu { + Button(model.localized("Name: Aโ€“Z")) { model.sortRuntimeFiles(.nameAsc) } + Button(model.localized("Name: Zโ€“A")) { model.sortRuntimeFiles(.nameDesc) } + Button(model.localized("Modified: newest first")) { model.sortRuntimeFiles(.modifiedDesc) } + Button(model.localized("Modified: oldest first")) { model.sortRuntimeFiles(.modifiedAsc) } + } label: { + Text(sortLabel(files.sort)).lineLimit(1).frame(maxWidth: .infinity, minHeight: 44, alignment: .leading) + }.accessibilityLabel(model.localized("Sort files")) + Menu { + Button(model.localized("ๆ–ฐๅปบๆ–‡ไปถ")) { begin(.file) } + Button(model.localized("ๅˆ›ๅปบๆ–‡ไปถๅคน")) { begin(.directory) } + Button(model.localized("Upload file")) { begin(.upload) } + } label: { Image(systemName: "plus").frame(width: 44, height: 44) }.accessibilityIdentifier("file.browser.create") + } + + }.padding(.horizontal, 16).disabled(busy) + if busy { ProgressView().padding(8) } + if files.failed && action == nil { Text(files.errorDetail ?? model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")).font(.caption).padding(8) } + ScrollView { + LazyVStack(spacing: 0) { + ForEach(files.entries, id: \.path) { entry in + HStack(spacing: 12) { + Button { + if entry.directory { model.browseRuntimeFiles(entry.path) } else { model.readRuntimeFile(entry.path) } + } label: { + HStack(spacing: 12) { + Image(systemName: entry.directory ? "folder" : "doc").foregroundStyle(OpenBitFunTheme.muted).frame(width: 22) + Text(entry.name).lineLimit(2).multilineTextAlignment(.leading) + Spacer(minLength: 0) + }.frame(minHeight: 52).contentShape(Rectangle()) + }.buttonStyle(.plain) + Menu { + if !entry.directory { Button(model.localized("ไธ‹่ฝฝ")) { model.downloadWorkspaceFile(path: entry.path, label: entry.name) } } + Button(model.localized("้‡ๅ‘ฝๅ")) { begin(.rename, path: entry.path, name: entry.name) } + Button(model.localized("ๅˆ ้™ค"), role: .destructive) { begin(.delete, path: entry.path, name: entry.name) } + } label: { Image(systemName: "ellipsis").frame(width: 44, height: 44) } + .accessibilityIdentifier("file.browser.actions.\(entry.path)") + }.disabled(busy) + Divider() + } + if files.hasMore { Button(model.localized("ๆ˜พ็คบๆ›ดๅคš")) { model.browseRuntimeFiles(files.directory, append: true) }.padding().disabled(busy) } + }.padding(.horizontal, 16) + } + .id("\(files.directory):\(files.sort.ordinal)") + } + } + .sheet(item: $action) { value in + VStack(spacing: 16) { + HStack(spacing: 12) { + Button(model.localized("ๅ–ๆถˆ")) { action = nil }.frame(minHeight: 44) + Spacer(minLength: 0) + Text(model.localized(value.label)).font(.headline) + Spacer(minLength: 0) + Button(model.localized(value.label), role: value == .delete ? .destructive : nil) { submit(value) } + .accessibilityIdentifier("file.action.submit").buttonStyle(.borderedProminent) + .disabled(submittedRevision != nil || (value != .delete && name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)) + }.disabled(busy) + ScrollView { + VStack(alignment: .leading, spacing: 16) { + if value == .delete { Text(target).font(.callout).foregroundStyle(OpenBitFunTheme.muted) } + else { TextField(model.localized("File name"), text: $name).textFieldStyle(.roundedBorder).autocorrectionDisabled().textInputAutocapitalization(.never).disabled(busy).accessibilityIdentifier("file.action.name") } + if model.runtimeFiles?.failed == true { Text(model.runtimeFiles?.errorDetail ?? model.localized("ๆ–‡ไปถๆ“ไฝœๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•ใ€‚")).font(.caption) } + if busy { ProgressView() } + }.frame(maxWidth: .infinity, alignment: .leading) + } + }.padding(24) + .presentationDetents([.height(240)]).background(OpenBitFunTheme.page) + .presentationDragIndicator(.visible).interactiveDismissDisabled(busy) + .tint(OpenBitFunTheme.ink) + .fileImporter(isPresented: $uploadPicker, allowedContentTypes: [.data], allowsMultipleSelection: false) { result in + guard model.remoteExpectedDeviceKey == deviceKey else { return } + if case .success(let urls) = result, let url = urls.first { + submittedRevision = model.runtimeFiles?.completedOperation + if !model.uploadRuntimeFileEntry(name, url: url) { submittedRevision = nil } + } else if case .failure = result { model.showToast(model.localized("Could not read the selected file. Choose a local file and retry.")) } + } + } + .onChange(of: model.runtimeFiles?.completedOperation) { revision in + if let submittedRevision, let revision, revision > submittedRevision { + if model.runtimeFiles?.failed != true { action = nil } + self.submittedRevision = nil + } + } + } + private func sortLabel(_ sort: RuntimeFileSort) -> String { + switch sort { + case .nameDesc: return model.localized("Name: Zโ€“A") + case .modifiedDesc: return model.localized("Modified: newest first") + case .modifiedAsc: return model.localized("Modified: oldest first") + default: return model.localized("Name: Aโ€“Z") + } + } + private func submit(_ value: RuntimeFileListAction) { + if value == .upload { uploadPicker = true; return } + submittedRevision = model.runtimeFiles?.completedOperation + switch value { + case .file: model.createRuntimeFileEntry(name, directory: false) + case .directory: model.createRuntimeFileEntry(name, directory: true) + case .rename: model.renameRuntimeFileEntry(target, name: name) + case .delete: model.deleteRuntimeFileEntry(target) + case .upload: break + } } } diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteFilePreviewView.swift b/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteFilePreviewView.swift index 3bd72387c4..163ebdca81 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteFilePreviewView.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Shell/RemoteFilePreviewView.swift @@ -328,6 +328,7 @@ struct RemoteFilePreviewSheet: View { .background(OpenBitFunTheme.page) .onAppear { highlightCode() } .onChange(of: preview) { _ in highlightCode() } + .modifier(RuntimeDownloadPresentation(model: model, enabled: model.runtimeDeviceTools?.visible != true)) .presentationDetents([.large]) .presentationDragIndicator(.visible) } diff --git a/src/apps/mobile/ios/OpenBitFun/Features/Shell/SidebarView.swift b/src/apps/mobile/ios/OpenBitFun/Features/Shell/SidebarView.swift index af059c8e03..56325e77e3 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/Shell/SidebarView.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/Shell/SidebarView.swift @@ -40,10 +40,6 @@ struct SidebarView: View { @State private var visibleDeviceWorkspaceCounts: [String: Int] = [:] @State private var compactActionSession: ChatSession? @State private var workspacePickerDevice: MobileDeviceDirectoryEntry? - @State private var deviceTools: NativeDeviceToolTarget? - @State private var deviceToolsPicker = false - @State private var toolLocation: NativeDeviceToolLocation? - @State private var pendingDeviceTool: NativeDeviceToolTarget? @State private var workspaceCreateTarget: MobileWorkspaceGroup? @State private var pendingWorkspaceCreate: NativeWorkspaceCreateTarget? @State private var workspaceCreatePath: String? @@ -125,31 +121,8 @@ struct SidebarView: View { } .onChange(of: model.remoteWorkspaces) { _ in finishWorkspaceCreateWhenReady() } .onChange(of: model.remoteCreateInteraction.canSubmit) { _ in finishWorkspaceCreateWhenReady() } - .sheet(isPresented: $deviceToolsPicker, onDismiss: { - if let target = pendingDeviceTool, target.deviceKey == model.remoteExpectedDeviceKey { - if target.terminal { model.openDeviceTerminal(target.location.path, connectionId: target.location.connectionId) } else { model.openDeviceFiles(target.location.path, connectionId: target.location.connectionId) } - deviceTools = target - } - pendingDeviceTool = nil - }) { - NavigationStack { - List { - if let location = toolLocation { - Text(location.name).font(.headline) - Button(model.localized("ๆต่งˆๆ–‡ไปถ")) { pendingDeviceTool = NativeDeviceToolTarget(location: location, terminal: false, deviceKey: model.remoteExpectedDeviceKey); deviceToolsPicker = false } - Button(model.localized("ๆ‰“ๅผ€็ปˆ็ซฏ")) { pendingDeviceTool = NativeDeviceToolTarget(location: location, terminal: true, deviceKey: model.remoteExpectedDeviceKey); deviceToolsPicker = false } - } else { - Button(model.localized("ๅ—ๆŽง่ฎพๅค‡ๆœฌๆœบ")) { toolLocation = NativeDeviceToolLocation(name: model.localized("ๅ—ๆŽง่ฎพๅค‡ๆœฌๆœบ"), path: "", connectionId: nil) } - ForEach(model.savedRuntimeConnections, id: \.id) { connection in - Button(connection.name) { toolLocation = NativeDeviceToolLocation(name: connection.name, path: "", connectionId: connection.id) } - } - } - }.navigationTitle(model.localized("Device tools")) - .toolbar { ToolbarItem(placement: .cancellationAction) { Button(model.localized("่ฟ”ๅ›ž")) { if toolLocation != nil { toolLocation = nil } else { deviceToolsPicker = false } } } } - } - } - .fullScreenCover(item: $deviceTools) { target in - NativeDeviceToolsView(model: model, terminal: target.terminal, rootPath: target.location.path, deviceKey: target.deviceKey) { deviceTools = nil } + .fullScreenCover(isPresented: Binding(get: { model.runtimeDeviceTools?.visible == true }, set: { if !$0 { model.closeDeviceTools() } })) { + NativeDeviceToolsView(model: model, rootPath: model.runtimeDeviceTools?.path ?? "", deviceKey: model.remoteExpectedDeviceKey) { model.closeDeviceTools() } } .sheet(item: $workspacePickerDevice) { requestedDevice in let device = directoryEntries.first(where: { $0.id == requestedDevice.id }) ?? requestedDevice @@ -320,18 +293,24 @@ struct SidebarView: View { .buttonStyle(.plain) .accessibilityLabel(Text(model.localized("ๅˆทๆ–ฐ่ฎพๅค‡"))) } - Button { model.scanRemote() } label: { - Image(systemName: "plus") - .font(.system(size: 16, weight: .regular)) - .frame(width: 17, height: 20) - .frame(width: 32, height: 32) - } - .buttonStyle(.plain) - .accessibilityLabel(Text(model.localized("ๆทปๅŠ ่ฟžๆŽฅ"))) + } .frame(height: 38) .padding(.top, 18) + if let error = model.accountDirectoryError { + Button { model.refreshRemoteDevices() } label: { + Text(error) + .font(.system(size: 12)) + .foregroundStyle(OpenBitFunTheme.statusDanger) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 8) + } + .buttonStyle(.plain) + .disabled(model.accountRefreshing) + .accessibilityHint(Text(model.localized("ๅˆทๆ–ฐ่ฎพๅค‡"))) + } + if directoryEntries.isEmpty { Text(model.localized("ๅฐšๆœช่ฟžๆŽฅๆกŒ้ข่ฎพๅค‡")) .font(.system(size: 13)) @@ -342,6 +321,26 @@ struct SidebarView: View { ForEach(directoryEntries) { device in directoryDeviceSelector(device) + if isCurrentDevice(device), model.connectionPhase != .connected { + HStack { + Text(activeConnectionLabel) + .font(MobileDesignTypography.labelSmall.font) + .foregroundStyle(OpenBitFunTheme.muted) + Spacer(minLength: 0) + Button { + model.retryRemoteConnection() + } label: { + Image(systemName: "arrow.clockwise") + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(OpenBitFunTheme.muted) + .frame(width: 44, height: 44) + } + .buttonStyle(.plain) + .accessibilityLabel(Text(model.localized("้‡่ฏ•"))) + .accessibilityIdentifier("sidebar.retryConnection") + } + .padding(.leading, 10) + } } if let selectedDirectoryEntry { @@ -374,14 +373,15 @@ struct SidebarView: View { @ViewBuilder private func directoryDeviceSelector(_ device: MobileDeviceDirectoryEntry) -> some View { - let selected = selectedDirectoryEntry?.id == device.id + let expanded = selectedDirectoryEntry?.id == device.id + let current = isCurrentDevice(device) Button { selectDirectoryDevice(device) } label: { HStack(spacing: 8) { Image(systemName: "desktopcomputer") .font(.system(size: 18, weight: .regular)) .frame(width: 24, height: 20) Text(device.name) - .font(.system(size: 15, weight: selected ? .medium : .regular)) + .font(.system(size: 14, weight: current ? .bold : .regular)) .foregroundStyle(device.online ? OpenBitFunTheme.ink : OpenBitFunTheme.muted) .lineLimit(1) Spacer(minLength: 0) @@ -389,10 +389,10 @@ struct SidebarView: View { ProgressView().controlSize(.small) } else { Circle() - .fill(device.online ? OpenBitFunTheme.statusSuccess : OpenBitFunTheme.muted) + .fill(current ? activeConnectionColor : (device.online ? OpenBitFunTheme.statusSuccess : OpenBitFunTheme.muted)) .frame(width: 8, height: 8) } - Image(systemName: selected ? "chevron.down" : "chevron.right") + Image(systemName: expanded ? "chevron.down" : "chevron.right") .font(.system(size: 13, weight: .medium)) .foregroundStyle(device.online ? OpenBitFunTheme.ink : OpenBitFunTheme.muted) .frame(width: 20, height: 24) @@ -407,7 +407,27 @@ struct SidebarView: View { .opacity(device.online ? 1 : 0.58) .accessibilityIdentifier("sidebar.device.\(device.id)") .accessibilityLabel(Text(device.name)) - .accessibilityValue(Text(device.online ? model.localized("ๅœจ็บฟ") : model.localized("็ฆป็บฟ"))) + .accessibilityValue(Text(current ? activeConnectionLabel : (device.online ? model.localized("ๅœจ็บฟ") : model.localized("็ฆป็บฟ")))) + } + + private func isCurrentDevice(_ device: MobileDeviceDirectoryEntry) -> Bool { + device.id == (model.accountSelectedDeviceID ?? normalizedDeviceKey(model.remoteExpectedDeviceKey)) + } + + private var activeConnectionLabel: String { + switch model.connectionPhase { + case .connected: return model.localized("ๅทฒ่ฟžๆŽฅ") + case .reconnecting: return model.localized("ๆญฃๅœจๆขๅค่ฟžๆŽฅ") + case .disconnected: return model.localized("ๅทฒๆ–ญๅผ€") + } + } + + private var activeConnectionColor: Color { + switch model.connectionPhase { + case .connected: return OpenBitFunTheme.statusSuccess + case .reconnecting: return OpenBitFunTheme.muted + case .disconnected: return OpenBitFunTheme.statusDanger + } } private func selectDirectoryDevice(_ device: MobileDeviceDirectoryEntry) { @@ -426,6 +446,12 @@ struct SidebarView: View { @ViewBuilder private func directoryDeviceBody(_ device: MobileDeviceDirectoryEntry) -> some View { + if device.catalogSource == "RECENT" { + Text(model.localized("ๆ—ง็‰ˆ่ฎพๅค‡๏ผšๆ˜พ็คบๆœ€่ฟ‘่ฎฟ้—ฎ็š„ๅทฅไฝœๅŒบ")) + .font(.system(size: 12)) + .foregroundStyle(OpenBitFunTheme.muted) + .padding(.horizontal, 10) + } if device.status == "LOADING" && device.workspaces.isEmpty && device.sessions.isEmpty { HStack(spacing: 8) { ProgressView().controlSize(.small); Text(model.localized("ๆญฃๅœจๅŠ ่ฝฝๅทฅไฝœๅŒบ")).font(.system(size: 13)).foregroundStyle(OpenBitFunTheme.muted) } .padding(.horizontal, 18).frame(height: 42) @@ -456,7 +482,8 @@ struct SidebarView: View { deviceKey: device.id, directoryExpanded: workspace.directoryExpanded, directoryStatus: workspace.directoryStatus, - remoteConnectionId: workspace.remoteConnectionId + remoteConnectionId: workspace.remoteConnectionId, + remoteSshHost: workspace.remoteSshHost ) SidebarWorkspaceRow( workspace: scopedWorkspace, @@ -574,7 +601,7 @@ struct SidebarView: View { return sources.map { source in MobileWorkspaceGroup(path: source.path, name: source.name, selected: source.selected, sessions: section.sessions, deviceKey: normalizedDeviceKey(model.remoteExpectedDeviceKey), - remoteConnectionId: source.remoteConnectionId) + remoteConnectionId: source.remoteConnectionId, remoteSshHost: source.remoteSshHost) } } return ForEach(workspaces) { workspace in @@ -880,7 +907,7 @@ struct SidebarView: View { private var authenticatedFooter: some View { HStack(spacing: 0) { - Button { toolLocation = nil; deviceToolsPicker = true } label: { deviceToolsLabel } + Button { model.openDeviceTools() } label: { deviceToolsLabel } .buttonStyle(.plain) .disabled(!model.remoteConnected) .accessibilityIdentifier("sidebar.deviceTools") @@ -939,7 +966,7 @@ private struct SidebarRecentRow: View { Button(action: onOpen) { VStack(alignment: .leading, spacing: 2) { Text(session.title) - .font(.system(size: 15, weight: selected ? .medium : .regular)) + .font(.system(size: 13, weight: selected ? .bold : .regular)) .foregroundStyle(OpenBitFunTheme.ink) .lineLimit(1) if let metadata, !metadata.isEmpty { @@ -1025,6 +1052,7 @@ private struct SidebarWorkspaceRow: View { .accessibilityLabel( MobileLocalization.text(expanded ? "ๆ”ถ่ตทๅทฅไฝœๅŒบ" : "ๅฑ•ๅผ€ๅทฅไฝœๅŒบ") ) + .accessibilityIdentifier("sidebar.workspaceDisclosure.\(workspace.deviceKey ?? "unknown").\(workspace.path)") Button(action: onToggle) { HStack(spacing: 10) { @@ -1039,7 +1067,7 @@ private struct SidebarWorkspaceRow: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - .onLongPressGesture(perform: onOpenWorkspace) + .simultaneousGesture(LongPressGesture().onEnded { _ in onOpenWorkspace() }) .accessibilityIdentifier("sidebar.workspace.\(workspace.deviceKey ?? "unknown").\(workspace.path)") .accessibilityValue(Text(workspace.path)) Spacer(minLength: 0) @@ -1116,8 +1144,8 @@ private struct SidebarWorkspaceRow: View { VStack(alignment: .leading, spacing: 2) { Text(session.title) .font(.system( - size: 15, - weight: isSelected(session) ? .medium : .regular + size: 13, + weight: isSelected(session) ? .bold : .regular )) .foregroundStyle(OpenBitFunTheme.ink) .lineLimit(1) @@ -1183,6 +1211,8 @@ private struct SidebarWorkspacePickerSheet: View { let onClose: () -> Void let onSelect: (MobileWorkspaceGroup) -> Void + private var workspaces: [MobileWorkspaceGroup] { device.recentWorkspaces ?? device.workspaces } + var body: some View { VStack(spacing: 0) { HStack(spacing: 12) { @@ -1212,7 +1242,7 @@ private struct SidebarWorkspacePickerSheet: View { Divider().overlay(OpenBitFunTheme.line) - if device.status == "LOADING" && device.workspaces.isEmpty { + if device.status == "LOADING" && workspaces.isEmpty { VStack(spacing: 12) { ProgressView().controlSize(.regular) Text(MobileLocalization.text("ๆญฃๅœจๅŠ ่ฝฝ")) @@ -1220,7 +1250,7 @@ private struct SidebarWorkspacePickerSheet: View { .foregroundStyle(OpenBitFunTheme.muted) } .frame(maxWidth: .infinity, maxHeight: .infinity) - } else if device.workspaces.isEmpty { + } else if workspaces.isEmpty { VStack(spacing: 8) { Image(systemName: "folder") .font(.system(size: 30, weight: .regular)) @@ -1234,7 +1264,7 @@ private struct SidebarWorkspacePickerSheet: View { } else { ScrollView(showsIndicators: false) { LazyVStack(spacing: 4) { - ForEach(device.workspaces) { workspace in + ForEach(workspaces) { workspace in Button { onSelect(workspace) } label: { HStack(spacing: 12) { Image(systemName: "folder") diff --git a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+Account.swift b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+Account.swift index bc4ef2cee4..343092707b 100644 --- a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+Account.swift +++ b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+Account.swift @@ -32,7 +32,7 @@ extension MobileAppModel { remoteCreateRequestEpoch = remoteTargetEpoch remoteCreateRequestDeviceKey = nil pendingRemoteWorkspaceCreate = nil - pendingRemoteSessionRefreshWorkspacePath = nil + pendingRemoteSessionRefreshWorkspace = nil pendingRemoteAssistantCreate = false remoteSessionSelected = false } @@ -67,8 +67,9 @@ extension MobileAppModel { remoteSessions = [] remoteWorkspaces = [] workspaceCatalog = [] + remoteSidebarWorkspaceState = nil pendingRemoteWorkspaceCreate = nil - pendingRemoteSessionRefreshWorkspacePath = nil + pendingRemoteSessionRefreshWorkspace = nil pendingRemoteAssistantCreate = false selectedRemoteWorkspaceKind = "" messages = [] @@ -130,6 +131,7 @@ extension MobileAppModel { remoteSessions = [] remoteWorkspaces = [] workspaceCatalog = [] + remoteSidebarWorkspaceState = nil workspaceSelectionBusy = false remoteCreateWorkspacePhase = .unavailable pendingRemoteWorkspaceCreate = nil @@ -196,6 +198,11 @@ extension MobileAppModel { guard !accountLoginPreview, !localActionPreview, !remoteCreatePreview, generation == accountGeneration else { return } accountGeneration = generation + if let ready = state as? AccountUiStateReady, let failure = ready.refreshFailure { + accountDirectoryError = accountErrorMessage(failure.name, stage: "DEVICE_LIST") + } else { + accountDirectoryError = nil + } accountBusy = state is AccountUiStateSigningIn || state is AccountUiStateAuthorizing let previousAuthorizationURL = accountAuthorizationURL accountAuthorizationURL = (state as? AccountUiStateAuthorizing).flatMap { authorization in @@ -260,9 +267,6 @@ extension MobileAppModel { connectionPhase = ready.selectedDeviceId == nil ? .disconnected : .reconnecting } surface = .remote - if ready.refreshFailure != nil { - showToast(localized("่ฎพๅค‡ๅˆ—่กจๅˆทๆ–ฐๅคฑ่ดฅ๏ผŒไปๆ˜พ็คบไธŠๆฌก็ป“ๆžœ")) - } } else if let failed = state as? AccountUiStateFailed { accountBusy = false accountFailureStage = failed.stage.name diff --git a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+FilePreview.swift b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+FilePreview.swift index da3654e941..fdbc48e8aa 100644 --- a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+FilePreview.swift +++ b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+FilePreview.swift @@ -13,8 +13,6 @@ private struct MobilePreviewRequestExpectation { private var filePreviewRequestByModel: [ObjectIdentifier: MobilePreviewRequestExpectation] = [:] private var downloadRetryableByModel: [ObjectIdentifier: Bool] = [:] -private var downloadRetrySessionByModel: [ObjectIdentifier: String] = [:] -private var downloadRetryEpochByModel: [ObjectIdentifier: Int32] = [:] private var downloadRetryDeviceByModel: [ObjectIdentifier: String] = [:] private var downloadRetryCallbackEpochByModel: [ObjectIdentifier: UInt64] = [:] private var downloadRetryPathByModel: [ObjectIdentifier: String] = [:] @@ -45,6 +43,7 @@ private func localizedFailureReason(_ localized: (String) -> String, kindName: S extension MobileAppModel { func invalidateTargetScopedFileTransfers() { + runtimeDeviceTools = nil let modelID = ObjectIdentifier(self) filePreviewRequestByModel[modelID] = nil filePreview = nil @@ -56,8 +55,6 @@ extension MobileAppModel { downloadStatusText = nil downloadPhase = .idle downloadRetryableByModel[modelID] = nil - downloadRetrySessionByModel[modelID] = nil - downloadRetryEpochByModel[modelID] = nil downloadRetryDeviceByModel[modelID] = nil downloadRetryCallbackEpochByModel[modelID] = nil downloadRetryPathByModel[modelID] = nil @@ -199,8 +196,6 @@ extension MobileAppModel { downloadPhase = .failed let modelID = ObjectIdentifier(self) downloadRetryableByModel[modelID] = failed.retryable - downloadRetrySessionByModel[modelID] = failed.target.sessionId - downloadRetryEpochByModel[modelID] = failed.target.controlTargetEpoch downloadRetryDeviceByModel[modelID] = coreAdapter?.currentRemoteTargetKey downloadRetryCallbackEpochByModel[modelID] = coreAdapter?.currentRemoteTargetEpoch ?? 0 downloadRetryPathByModel[modelID] = failed.target.remotePath @@ -219,21 +214,13 @@ extension MobileAppModel { guard downloadRetryableByModel[modelID] == true, let path = downloadRetryPathByModel[modelID], downloadTargetPath == path, - downloadRetrySessionByModel[modelID] == selectedSessionID, downloadRetryDeviceByModel[modelID] == coreAdapter?.currentRemoteTargetKey, downloadRetryCallbackEpochByModel[modelID] == coreAdapter?.currentRemoteTargetEpoch else { downloadPhase = .failed downloadStatusText = localized("ไธ‹่ฝฝ็›ฎๆ ‡ๅทฒๅ˜ๅŒ–๏ผŒ่ฏท้‡ๆ–ฐๆ‰“ๅผ€ๆ–‡ไปถ") return } - if let preview = filePreview, preview.id == path, - preview.sessionID == downloadRetrySessionByModel[modelID], - preview.controlTargetEpoch != downloadRetryEpochByModel[modelID] { - downloadPhase = .failed - downloadStatusText = localized("ไธ‹่ฝฝ็›ฎๆ ‡ๅทฒๅ˜ๅŒ–๏ผŒ่ฏท้‡ๆ–ฐๆ‰“ๅผ€ๆ–‡ไปถ") - return - } - downloadRemoteFile(reference: "computer://\(path)", label: path.split(separator: "/").last.map(String.init) ?? path) + coreAdapter?.retryRemoteDownload() } func apply(filePreviewState state: RemoteFilePreviewUiState) { diff --git a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+RemoteSession.swift b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+RemoteSession.swift index 823daf4f2c..bc7ba8014b 100644 --- a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+RemoteSession.swift +++ b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel+RemoteSession.swift @@ -70,6 +70,7 @@ extension MobileAppModel { remoteWorkspaces = [] remoteAssistants = [] workspaceCatalog = [] + remoteSidebarWorkspaceState = nil selectedRemoteWorkspaceKind = "" workspaceLoading = !targetKey.isEmpty workspaceLoadFailed = false @@ -103,7 +104,7 @@ extension MobileAppModel { pendingDirectoryRemoteDraft = nil } pendingRemoteWorkspaceCreate = nil - pendingRemoteSessionRefreshWorkspacePath = nil + pendingRemoteSessionRefreshWorkspace = nil pendingRemoteAssistantCreate = false committedRemoteCreate = nil @@ -131,23 +132,31 @@ extension MobileAppModel { agentType: session.agentType, workspacePath: session.workspacePath, workspaceName: session.workspaceName, + workspaceScope: session.workspaceIdentity.map { + MobileWorkspaceScope(path: $0.path, remoteConnectionId: $0.remoteConnectionId, remoteSshHost: $0.remoteSshHost) + }, deviceKey: deviceKey, createdAt: session.createdAt, messageCount: Int(session.messageCount) ) } let workspaces = entry.workspaces.map { workspace in - let directory = entry.workspace(path: workspace.path) + let directory = entry.workspace(path: workspace.path, remoteConnectionId: workspace.remoteConnectionId, remoteSshHost: workspace.remoteSshHost) + let ownedSessionIDs = Set(entry.sessionsForWorkspace(path: workspace.path, remoteConnectionId: workspace.remoteConnectionId, remoteSshHost: workspace.remoteSshHost).map { $0.id }) return MobileWorkspaceGroup( path: workspace.path, name: workspace.name.isEmpty ? workspace.path : workspace.displayName, selected: remoteExpectedDeviceKey == deviceKey && - normalizedSessionWorkspacePath(workspace.path) == normalizedSessionWorkspacePath(workspaceCatalog.first(where: { $0.selected })?.path ?? ""), - sessions: sessions.filter { normalizedSessionWorkspacePath($0.workspacePath ?? "") == normalizedSessionWorkspacePath(workspace.path) }, + workspaceCatalog.contains(where: { + $0.selected && normalizedSessionWorkspacePath($0.path) == normalizedSessionWorkspacePath(workspace.path) && + $0.remoteConnectionId == workspace.remoteConnectionId && $0.remoteSshHost == workspace.remoteSshHost + }), + sessions: sessions.filter { ownedSessionIDs.contains($0.id) }, deviceKey: deviceKey, directoryExpanded: directory?.expanded ?? false, directoryStatus: directory?.status.name ?? "IDLE", - remoteConnectionId: workspace.remoteConnectionId + remoteConnectionId: workspace.remoteConnectionId, + remoteSshHost: workspace.remoteSshHost ) } return MobileDeviceDirectoryEntry( @@ -158,7 +167,13 @@ extension MobileAppModel { status: entry.status.name, error: entry.error?.name, workspaces: workspaces, - sessions: sessions + sessions: sessions, + catalogSource: entry.catalogSource?.name, + recentWorkspaces: entry.recentWorkspaces.map { workspace in + MobileWorkspaceGroup(path: workspace.path, name: workspace.displayName, + selected: false, sessions: [], deviceKey: deviceKey, + remoteConnectionId: workspace.remoteConnectionId, remoteSshHost: workspace.remoteSshHost) + } ) } } @@ -184,7 +199,7 @@ extension MobileAppModel { expanded: Bool ) { guard device.online else { return } - coreAdapter?.setDirectoryWorkspaceExpanded(device.id, path: workspace.path, expanded: expanded) + coreAdapter?.setDirectoryWorkspaceExpanded(device.id, path: workspace.path, expanded: expanded, connectionId: workspace.remoteConnectionId, sshHost: workspace.remoteSshHost) } func retryDirectoryWorkspace( @@ -192,7 +207,7 @@ extension MobileAppModel { workspace: MobileWorkspaceGroup ) { guard device.online else { return } - coreAdapter?.retryDirectoryWorkspace(device.id, path: workspace.path) + coreAdapter?.retryDirectoryWorkspace(device.id, path: workspace.path, connectionId: workspace.remoteConnectionId, sshHost: workspace.remoteSshHost) } private func directoryTargetKey(forRawDeviceKey rawDeviceKey: String) -> String { @@ -238,6 +253,8 @@ extension MobileAppModel { rawDeviceKey: device.id, workspacePath: workspace.path, normalizedWorkspacePath: normalizedSessionWorkspacePath(workspace.path), + remoteConnectionId: workspace.remoteConnectionId, + remoteSshHost: workspace.remoteSshHost, agentType: agentType, epoch: epoch, selectionRequested: false @@ -271,7 +288,7 @@ extension MobileAppModel { selectRemoteWorkspace(workspace) return } - pendingDirectoryWorkspace = (deviceKey, workspace.path, remoteTargetEpoch &+ 1) + pendingDirectoryWorkspace = (deviceKey, workspace.path, remoteTargetEpoch &+ 1, workspace.remoteConnectionId, workspace.remoteSshHost) guard targetKey != "pairing", let device = accountDevices.first(where: { $0.id == deviceKey }) else { pendingDirectoryWorkspace = nil @@ -373,6 +390,8 @@ extension MobileAppModel { isSending = false busy = true remoteHasMoreMessages = false + remoteHistoryLoading = false + remoteHistoryFailed = false remoteConversationLoadTask = Task { [weak self] in do { try await Task.sleep(nanoseconds: 140_000_000) @@ -420,8 +439,9 @@ extension MobileAppModel { showToast(localized("่ฟœ็จ‹ไผš่ฏ่ฟžๆŽฅๅทฒๅคฑๆ•ˆ๏ผŒ่ฏท้‡ๆ–ฐ้€‰ๆ‹ฉ่ฎพๅค‡ๅŽ้‡่ฏ•")) return } - let selectedPath = workspaceCatalog.first(where: { $0.selected })?.path ?? "" - if selectedPath == pending.workspacePath { + let selected = remoteSidebarWorkspaceState?.selected + if normalizedSessionWorkspacePath(selected?.path ?? "") == pending.normalizedWorkspacePath && + selected?.remoteConnectionId == pending.remoteConnectionId && selected?.remoteSshHost == pending.remoteSshHost { guard remoteCreateInteraction.canSubmit else { mobilePerformanceLog.error("Directory create blocked connected=\(self.remoteConnected) switching=\(self.accountBusy) workspaceReady=\(self.remoteCreateWorkspacePhase == .ready) selecting=\(self.workspaceSelectionBusy) submitting=\(self.remoteCreateSubmitting) activeTurn=\(self.activeTurnID != nil) sending=\(self.isSending)") pendingDirectoryRemoteDraft = nil @@ -433,12 +453,14 @@ extension MobileAppModel { drawerOpen = false createRemoteSession( agentType: pending.agentType, title: "", instruction: "", - workspacePath: pending.workspacePath + workspacePath: pending.workspacePath, + remoteConnectionId: pending.remoteConnectionId, remoteSshHost: pending.remoteSshHost ) return } guard !pending.selectionRequested else { return } - guard workspaceCatalog.contains(where: { $0.path == pending.workspacePath }) else { + guard workspaceCatalog.contains(where: { $0.path == pending.workspacePath && + $0.remoteConnectionId == pending.remoteConnectionId && $0.remoteSshHost == pending.remoteSshHost }) else { pendingDirectoryRemoteDraft = nil showToast(localized("ๆš‚ๆ— ๅฏ็”จๅทฅไฝœๅŒบ")) return @@ -450,15 +472,19 @@ extension MobileAppModel { } pending.selectionRequested = true pendingDirectoryRemoteDraft = pending - coreAdapter?.selectRemoteWorkspace(path: pending.workspacePath) + coreAdapter?.selectRemoteWorkspace(path: pending.workspacePath, remoteConnectionId: pending.remoteConnectionId, remoteSshHost: pending.remoteSshHost) } func resizeRuntimeTerminal(cols: Int, rows: Int) { coreAdapter?.resizeRuntimeTerminal(cols: cols, rows: rows) } + func openDeviceTools(connectionId: String? = nil) { coreAdapter?.openDeviceTools(connectionId: connectionId) } + func selectDeviceToolsPanel(terminal: Bool) { coreAdapter?.selectDeviceToolsPanel(terminal: terminal) } + func closeDeviceTools() { coreAdapter?.closeDeviceTools() } + func startDeviceToolsTerminal() { coreAdapter?.startDeviceToolsTerminal() } func openDeviceFiles(_ path: String, connectionId: String?) { coreAdapter?.openDeviceFiles(path, connectionId: connectionId) } func openDeviceTerminal(_ path: String, connectionId: String?) { coreAdapter?.openDeviceTerminal(path, connectionId: connectionId) } func browseRuntimeDirectories(_ path: String, connectionId: String?, append: Bool = false) { coreAdapter?.browseRuntimeDirectories(path, connectionId: connectionId, append: append) } func sortRuntimeFiles(_ sort: RuntimeFileSort) { coreAdapter?.sortRuntimeFiles(sort) } - func closeRuntimeFileEditor() { coreAdapter?.closeRuntimeFileEditor() } + func closeRuntimeFileEditor() { runtimeFileDraft.reset(); coreAdapter?.closeRuntimeFileEditor() } func browseRuntimeFiles(_ path: String, append: Bool = false) { coreAdapter?.browseRuntimeFiles(path, append: append) } func readRuntimeFile(_ path: String) { coreAdapter?.readRuntimeFile(path) } func saveRuntimeFile(_ content: String) { coreAdapter?.saveRuntimeFile(content) } @@ -469,22 +495,34 @@ extension MobileAppModel { coreAdapter.uploadRuntimeFile(path, source: source) } catch { showToast(localized("Could not read the selected file. Choose a local file and retry.")) } } + func createRuntimeFileEntry(_ name: String, directory: Bool) { coreAdapter?.createRuntimeFileEntry(name, directory: directory) } + func renameRuntimeFileEntry(_ path: String, name: String) { coreAdapter?.renameRuntimeFileEntry(path, name: name) } + func deleteRuntimeFileEntry(_ path: String) { coreAdapter?.deleteRuntimeFileEntry(path) } + func uploadRuntimeFileEntry(_ name: String, url: URL) -> Bool { + do { + let source = try IOSRuntimeUploadSource(url: url) + guard let coreAdapter else { source.close(); return false } + coreAdapter.uploadRuntimeFileEntry(name, source: source) + return true + } catch { showToast(localized("Could not read the selected file. Choose a local file and retry.")); return false } + } func createRuntimeFile(_ path: String) { coreAdapter?.createRuntimeFile(path) } func renameRuntimeFile(_ path: String) { coreAdapter?.renameRuntimeFile(path) } func deleteRuntimeFile() { coreAdapter?.deleteRuntimeFile() } func createRuntimeDirectory(_ path: String) { coreAdapter?.createRuntimeDirectory(path) } func openRuntimeTerminal() { coreAdapter?.openRuntimeTerminal() } + func reopenRuntimeTerminal() { coreAdapter?.reopenRuntimeTerminal() } func writeRuntimeTerminal(_ data: String) { coreAdapter?.writeRuntimeTerminal(data) } func closeRuntimeTerminal() { coreAdapter?.closeRuntimeTerminal() } - func openRemoteWorkspacePath(_ path: String, connectionId: String?) { + func openRemoteWorkspacePath(_ path: String, connectionId: String?, sshHost: String? = nil) { let targetPath = path.trimmingCharacters(in: .whitespacesAndNewlines) guard !targetPath.isEmpty, remoteConnected, remoteCreateInteraction.canSelectWorkspace else { return } pendingDirectoryRemoteDraft = nil surface = .remote workspaceSelectionBusy = true - pendingRemoteSessionRefreshWorkspacePath = normalizedSessionWorkspacePath(targetPath) - coreAdapter?.selectRemoteWorkspace(path: targetPath, remoteConnectionId: connectionId) + pendingRemoteSessionRefreshWorkspace = MobileWorkspaceScope(path: normalizedSessionWorkspacePath(targetPath), remoteConnectionId: connectionId, remoteSshHost: sshHost) + coreAdapter?.selectRemoteWorkspace(path: targetPath, remoteConnectionId: connectionId, remoteSshHost: sshHost) } func selectRemoteWorkspace(_ workspace: MobileWorkspaceGroup) { @@ -496,8 +534,8 @@ extension MobileAppModel { surface = .remote drawerOpen = false workspaceSelectionBusy = true - pendingRemoteSessionRefreshWorkspacePath = normalizedSessionWorkspacePath(workspace.path) - coreAdapter?.selectRemoteWorkspace(path: workspace.path) + pendingRemoteSessionRefreshWorkspace = MobileWorkspaceScope(path: normalizedSessionWorkspacePath(workspace.path), remoteConnectionId: workspace.remoteConnectionId, remoteSshHost: workspace.remoteSshHost) + coreAdapter?.selectRemoteWorkspace(path: workspace.path, remoteConnectionId: workspace.remoteConnectionId, remoteSshHost: workspace.remoteSshHost) } func createRemoteSession(in workspace: MobileWorkspaceGroup, agentType: String) { @@ -510,7 +548,8 @@ extension MobileAppModel { title: "", instruction: "", workspacePath: workspace.path, - remoteConnectionId: workspace.remoteConnectionId + remoteConnectionId: workspace.remoteConnectionId, + remoteSshHost: workspace.remoteSshHost ) } @@ -546,7 +585,8 @@ extension MobileAppModel { instruction: String, modelID: String? = nil, workspacePath: String? = nil, - remoteConnectionId: String? = nil + remoteConnectionId: String? = nil, + remoteSshHost: String? = nil ) { guard remoteCreateInteraction.canSubmit else { remoteCreateError = localized("่ฟœ็จ‹ไผš่ฏๅฝ“ๅ‰ไธๅฏๅˆ›ๅปบ๏ผŒ่ฏท้‡่ฏ•") @@ -576,7 +616,8 @@ extension MobileAppModel { instruction: normalizedInstruction, modelID: selectedModel, workspacePath: workspacePath, - remoteConnectionId: remoteConnectionId + remoteConnectionId: remoteConnectionId, + remoteSshHost: remoteSshHost ) surface = .remote } @@ -633,6 +674,9 @@ extension MobileAppModel { agentType: confirmed.agentType, workspacePath: confirmed.workspacePath, workspaceName: confirmed.workspaceName, + workspaceScope: confirmed.workspaceIdentity.map { + MobileWorkspaceScope(path: $0.path, remoteConnectionId: $0.remoteConnectionId, remoteSshHost: $0.remoteSshHost) + }, createdAt: confirmed.createdAt, messageCount: Int(confirmed.messageCount) ) @@ -714,6 +758,14 @@ extension MobileAppModel { coreAdapter?.refreshRemoteSessions() } + func retryRemoteConnection() { + // Recovery must reach the shared stores even while the link is down. + // The adapter owns the current target; stores preserve the transcript + // and reject superseded work through their existing lifecycle fences. + coreAdapter?.refreshRemoteSessions() + coreAdapter?.loadRemoteWorkspaces() + } + func setRemoteAgentFilter(_ name: String) { let filter: SessionAgentFilter switch name { @@ -939,6 +991,9 @@ extension MobileAppModel { agentType: session.agentType, workspacePath: session.workspacePath, workspaceName: session.workspaceName, + workspaceScope: session.workspaceIdentity.map { + MobileWorkspaceScope(path: $0.path, remoteConnectionId: $0.remoteConnectionId, remoteSshHost: $0.remoteSshHost) + }, createdAt: session.createdAt, messageCount: Int(session.messageCount) ) @@ -977,6 +1032,8 @@ extension MobileAppModel { setPublishedIfChanged(\.remoteAgentFilter, to: ready.agentFilter.name) setPublishedIfChanged(\.remoteHasMore, to: ready.hasMore) setPublishedIfChanged(\.remoteHasMoreMessages, to: ready.hasMoreMessages) + setPublishedIfChanged(\.remoteHistoryLoading, to: ready.historyLoadState == .loading) + setPublishedIfChanged(\.remoteHistoryFailed, to: ready.historyLoadState == .failed) setPublishedIfChanged(\.remotePermissionMode, to: ready.permissionMode?.name ?? remotePermissionMode) setPublishedIfChanged(\.remotePermissionFailure, to: ready.permissionModeFailure?.name) let acceptsTimeline = remoteConversationOpeningSessionID.map { @@ -1031,8 +1088,8 @@ extension MobileAppModel { remoteConnected, remoteInitialWorkspaceReady { pendingDirectoryWorkspace = nil - pendingRemoteSessionRefreshWorkspacePath = normalizedSessionWorkspacePath(pending.path) - coreAdapter?.selectRemoteWorkspace(path: pending.path) + pendingRemoteSessionRefreshWorkspace = MobileWorkspaceScope(path: normalizedSessionWorkspacePath(pending.path), remoteConnectionId: pending.remoteConnectionId, remoteSshHost: pending.remoteSshHost) + coreAdapter?.selectRemoteWorkspace(path: pending.path, remoteConnectionId: pending.remoteConnectionId, remoteSshHost: pending.remoteSshHost) } openPendingDirectorySessionIfReady() advancePendingDirectoryRemoteDraftIfReady() @@ -1043,6 +1100,10 @@ extension MobileAppModel { targetKey: String, epoch: UInt64 ) { + #if DEBUG + let targetMatches = targetKey == remoteExpectedDeviceKey + mobilePerformanceLog.info("Remote health model: phase=\(phase.name, privacy: .public) epoch=\(epoch) expectedEpoch=\(self.remoteTargetEpoch) targetMatches=\(targetMatches) preview=\(self.localActionPreview || self.accountLoginPreview || self.remoteCreatePreview)") + #endif guard !localActionPreview, !accountLoginPreview, !remoteCreatePreview, RemoteAuthorityGate.callbackMatchesAuthority( targetKey: targetKey, @@ -1095,7 +1156,7 @@ extension MobileAppModel { busy = false showToast(localized("ๅทฅไฝœๅŒบๅŠ ่ฝฝๅคฑ่ดฅ๏ผŒ็‚นๆŒ‰้‡่ฏ•")) } - pendingRemoteSessionRefreshWorkspacePath = nil + pendingRemoteSessionRefreshWorkspace = nil if pendingRemoteWorkspaceCreate != nil || pendingRemoteAssistantCreate || pendingDirectoryRemoteDraft != nil { pendingRemoteWorkspaceCreate = nil @@ -1107,8 +1168,15 @@ extension MobileAppModel { } guard let ready = state as? RemoteWorkspaceUiStateReady else { return } + runtimeDeviceTools = ready.deviceTools runtimeTerminal = ready.terminal runtimeFiles = ready.files + if let file = ready.files.file { + runtimeFileDraft.synchronize( + identity: [remoteExpectedDeviceKey ?? "", ready.deviceTools.connectionId ?? "", file], + savedContent: ready.files.content + ) + } runtimeDirectoryPicker = ready.directoryPicker savedRuntimeConnections = ready.savedConnections savedRuntimeConnectionsFailed = ready.savedConnectionsFailure @@ -1119,25 +1187,10 @@ extension MobileAppModel { remoteCreateWorkspacePhase = ready.loadFailure ? .failed : (ready.busy ? .loading : .ready) remoteInitialWorkspaceReady = !ready.busy && !ready.loadFailure selectedRemoteWorkspaceKind = ready.selected?.kind ?? "" - var seen = Set() - var catalog: [(path: String, name: String, selected: Bool, remoteConnectionId: String?)] = [] - if let selected = ready.selected, - !selected.path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - let normalizedPath = normalizedSessionWorkspacePath(selected.path) - seen.insert(normalizedPath + ":" + (selected.remoteConnectionId ?? "")) - catalog.append((selected.path, ready.workspaces.first(where: { normalizedSessionWorkspacePath($0.path) == normalizedPath })?.displayName ?? selected.name, true, selected.remoteConnectionId)) - } - for workspace in ready.workspaces { - guard !workspace.path.isEmpty else { continue } - let normalizedPath = normalizedSessionWorkspacePath(workspace.path) - guard seen.insert(normalizedPath + ":" + (workspace.remoteConnectionId ?? "")).inserted else { continue } - catalog.append((workspace.path, workspace.displayName, false, workspace.remoteConnectionId)) - } - for assistant in ready.assistants { - let key = normalizedSessionWorkspacePath(assistant.path) + ":" - if seen.insert(key).inserted { catalog.append((assistant.path, assistant.name, false, nil)) } - } - workspaceCatalog = catalog + remoteSidebarWorkspaceState = ready + workspaceCatalog = RemoteSidebarPresentation.shared.workspacesForSessions( + workspaceState: ready, sessions: [] + ).map { ($0.path, $0.name, $0.selected, $0.remoteConnectionId, $0.remoteSshHost) } remoteAssistants = ready.assistants.map { MobileAssistantOption(path: $0.path, name: $0.name) } @@ -1155,9 +1208,11 @@ extension MobileAppModel { pendingRemoteAssistantCreate = false createRemoteSession(agentType: "Claw", title: "", instruction: "") } - if !ready.busy, let pendingPath = pendingRemoteSessionRefreshWorkspacePath { - pendingRemoteSessionRefreshWorkspacePath = nil - if normalizedSessionWorkspacePath(ready.selected?.path ?? "") == pendingPath { + if !ready.busy, let pendingScope = pendingRemoteSessionRefreshWorkspace { + pendingRemoteSessionRefreshWorkspace = nil + if normalizedSessionWorkspacePath(ready.selected?.path ?? "") == pendingScope.path && + ready.selected?.remoteConnectionId == pendingScope.remoteConnectionId && + ready.selected?.remoteSshHost == pendingScope.remoteSshHost { coreAdapter?.refreshRemoteSessions() } } @@ -1165,17 +1220,18 @@ extension MobileAppModel { } func rebuildRemoteWorkspaceGroups() { - let selectedPath = workspaceCatalog.first(where: { $0.selected })?.path - let projectedWorkspaces = workspaceCatalog.map { workspace in - MobileWorkspaceGroup( + let rows = RemoteSidebarPresentation.shared.workspacesForSessions( + workspaceState: remoteSidebarWorkspaceState, sessions: sessionListCoreSessions + ) + let projectedWorkspaces = rows.map { workspace in + let ownedIDs = Set(workspace.sessions.map { $0.id }) + return MobileWorkspaceGroup( path: workspace.path, name: workspace.name.isEmpty ? workspace.path : workspace.name, selected: workspace.selected, - sessions: remoteSessions.filter { session in - normalizedSessionWorkspacePath(session.workspacePath ?? selectedPath ?? "") == - normalizedSessionWorkspacePath(workspace.path) - }, - remoteConnectionId: workspace.remoteConnectionId + sessions: remoteSessions.filter { ownedIDs.contains($0.id) }, + remoteConnectionId: workspace.remoteConnectionId, + remoteSshHost: workspace.remoteSshHost ) } setPublishedIfChanged(\.remoteWorkspaces, to: projectedWorkspaces) @@ -1265,7 +1321,10 @@ extension MobileAppModel { createdAt: session.createdAt, messageCount: Int32(session.messageCount), workspacePath: session.workspacePath, - workspaceName: session.workspaceName + workspaceName: session.workspaceName, + workspaceIdentity: session.workspaceScope.map { + RemoteWorkspaceIdentity(path: $0.path, remoteConnectionId: $0.remoteConnectionId, remoteSshHost: $0.remoteSshHost) + } ) } } diff --git a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel.swift b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel.swift index 393df32e02..688cc44122 100644 --- a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel.swift +++ b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileAppModel.swift @@ -20,6 +20,8 @@ final class MobileAppModel: ObservableObject { @Published var remoteViewSettingsOpen = false @Published var remoteHasMore = false @Published var remoteHasMoreMessages = false + @Published var remoteHistoryLoading = false + @Published var remoteHistoryFailed = false @Published var permissionMailbox: PermissionMailboxUiState? @Published var remoteConversationLoading = false @Published var remotePermissionMode = "ASK" @@ -77,10 +79,13 @@ final class MobileAppModel: ObservableObject { @Published var accountDeviceCount = 0 @Published var accountDevices: [MobileAccountDevice] = [] @Published var accountSelectedDeviceID: String? + @Published var accountDirectoryError: String? @Published var accountRefreshing = false @Published var deviceDirectory: [MobileDeviceDirectoryEntry] = [] @Published var runtimeFiles: RuntimeFilesUiState? + let runtimeFileDraft = RuntimeFileDraftState() @Published var runtimeDirectoryPicker: RuntimeFilesUiState? + @Published var runtimeDeviceTools: DeviceToolsUiState? @Published var runtimeTerminal: RuntimeTerminalUiState? @Published var savedRuntimeConnections: [SavedRuntimeConnectionUiState] = [] @Published var savedRuntimeConnectionsFailed = false @@ -120,10 +125,11 @@ final class MobileAppModel: ObservableObject { var remoteCreateRequestDeviceKey: String? var committedRemoteCreate: CommittedRemoteCreate? var remoteLastAppliedAuthority: RemoteAuthorityScope? - var workspaceCatalog: [(path: String, name: String, selected: Bool, remoteConnectionId: String?)] = [] + var remoteSidebarWorkspaceState: RemoteWorkspaceUiStateReady? + var workspaceCatalog: [(path: String, name: String, selected: Bool, remoteConnectionId: String?, remoteSshHost: String?)] = [] var pendingRemoteWorkspaceCreate: (path: String, agentType: String)? - var pendingRemoteSessionRefreshWorkspacePath: String? - var pendingDirectoryWorkspace: (deviceKey: String, path: String, epoch: UInt64)? + var pendingRemoteSessionRefreshWorkspace: MobileWorkspaceScope? + var pendingDirectoryWorkspace: (deviceKey: String, path: String, epoch: UInt64, remoteConnectionId: String?, remoteSshHost: String?)? var pendingDirectoryRemoteDraft: PendingDirectoryRemoteDraft? var pendingRemoteAssistantCreate = false var selectedRemoteWorkspaceKind = "" @@ -273,6 +279,7 @@ final class MobileAppModel: ObservableObject { remoteSessions = [] remoteWorkspaces = [] workspaceCatalog = [] + remoteSidebarWorkspaceState = nil remoteInitialSessionReady = false remoteInitialWorkspaceReady = false workspaceLoading = false @@ -280,7 +287,7 @@ final class MobileAppModel: ObservableObject { workspaceSelectionBusy = false remoteCreateWorkspacePhase = .unavailable pendingRemoteWorkspaceCreate = nil - pendingRemoteSessionRefreshWorkspacePath = nil + pendingRemoteSessionRefreshWorkspace = nil pendingDirectoryRemoteDraft = nil pendingRemoteAssistantCreate = false selectedRemoteWorkspaceKind = "" @@ -367,6 +374,7 @@ final class MobileAppModel: ObservableObject { remotePermissionFailure = nil sessionDetails = nil workspaceCatalog = [] + remoteSidebarWorkspaceState = nil workspaceLoading = false workspaceLoadFailed = false workspaceSelectionBusy = false @@ -375,7 +383,7 @@ final class MobileAppModel: ObservableObject { pendingDirectoryWorkspace = nil pendingDirectoryRemoteDraft = nil pendingRemoteWorkspaceCreate = nil - pendingRemoteSessionRefreshWorkspacePath = nil + pendingRemoteSessionRefreshWorkspace = nil pendingRemoteAssistantCreate = false selectedRemoteWorkspaceKind = "" selectedSessionID = "" @@ -448,3 +456,24 @@ final class MobileAppModel: ObservableObject { } } + +/// Presentation draft outlives temporary sheet reconstruction and scene transitions. +@MainActor +final class RuntimeFileDraftState: ObservableObject { + @Published var content = "" + private var identity: [String]? + private var savedContent: String? + + func synchronize(identity: [String], savedContent: String) { + guard self.identity != identity || self.savedContent != savedContent else { return } + self.identity = identity + self.savedContent = savedContent + content = savedContent + } + + func reset() { + identity = nil + savedContent = nil + content = "" + } +} diff --git a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileCoreAdapter.swift b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileCoreAdapter.swift index 5c6fedf2a5..15b83b209c 100644 --- a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileCoreAdapter.swift +++ b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileCoreAdapter.swift @@ -14,6 +14,9 @@ final class MobileCoreAdapter { private var foreground = true func setForeground(_ active: Bool) { foreground = active + #if DEBUG + log.info("Remote health lifecycle: foreground=\(active) bound=\(self.remoteSession != nil)") + #endif remoteSession?.dispatch(intent: RemoteSessionIntentSetForeground(active: active)) } private var remoteSession: RemoteSessionStore? @@ -77,7 +80,7 @@ final class MobileCoreAdapter { service: "com.openbitfun.mobile.account", deviceId: installID, deviceName: "OpenBitFun iPhone", - log: CoreLogNone.shared + log: MobileCoreLog() ) self.deviceDirectory = DeviceDirectoryStore.companion.create(scope: scope, accountStore: account) self.onAccountState = onAccountState @@ -204,18 +207,18 @@ final class MobileCoreAdapter { deviceDirectory.dispatch(intent: DeviceDirectoryIntentRetry(deviceId: deviceID)) } - func setDirectoryWorkspaceExpanded(_ deviceID: String, path: String, expanded: Bool) { + func setDirectoryWorkspaceExpanded(_ deviceID: String, path: String, expanded: Bool, connectionId: String? = nil, sshHost: String? = nil) { deviceDirectory.dispatch(intent: DeviceDirectoryIntentSetWorkspaceExpanded( deviceId: deviceID, path: path, - expanded: expanded + expanded: expanded, remoteConnectionId: connectionId, remoteSshHost: sshHost )) } - func retryDirectoryWorkspace(_ deviceID: String, path: String) { + func retryDirectoryWorkspace(_ deviceID: String, path: String, connectionId: String? = nil, sshHost: String? = nil) { deviceDirectory.dispatch(intent: DeviceDirectoryIntentRetryWorkspace( deviceId: deviceID, - path: path + path: path, remoteConnectionId: connectionId, remoteSshHost: sshHost )) } @@ -326,7 +329,8 @@ final class MobileCoreAdapter { instruction: String, modelID: String?, workspacePath: String? = nil, - remoteConnectionId: String? = nil + remoteConnectionId: String? = nil, + remoteSshHost: String? = nil ) { guard let remoteSession else { log.error("Remote create unavailable target_kind=\(self.remoteTargetKind(self.remoteTargetKey), privacy: .public)") @@ -343,7 +347,8 @@ final class MobileCoreAdapter { instruction: instruction, modelId: modelID, workspacePath: workspacePath, - remoteConnectionId: remoteConnectionId + remoteConnectionId: remoteConnectionId, + remoteSshHost: remoteSshHost ) ) } @@ -409,6 +414,10 @@ final class MobileCoreAdapter { } func resizeRuntimeTerminal(cols: Int, rows: Int) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentResizeTerminal(cols: Int32(cols), rows: Int32(rows))) } + func openDeviceTools(connectionId: String?) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentOpenDeviceTools(path: "", connectionId: connectionId)) } + func selectDeviceToolsPanel(terminal: Bool) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentSelectDeviceToolsPanel(panel: terminal ? .terminal : .files)) } + func closeDeviceTools() { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentCloseDeviceTools.shared) } + func startDeviceToolsTerminal() { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentStartDeviceToolsTerminal.shared) } func openDeviceFiles(_ path: String, connectionId: String?) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentOpenDeviceFiles(path: path, remoteConnectionId: connectionId)) } func openDeviceTerminal(_ path: String, connectionId: String?) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentOpenDeviceTerminal(path: path, remoteConnectionId: connectionId)) } func browseRuntimeDirectories(_ path: String, connectionId: String?, append: Bool) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentBrowseWorkspaceDirectories(path: path, remoteConnectionId: connectionId, append: append)) } @@ -418,12 +427,17 @@ final class MobileCoreAdapter { func readRuntimeFile(_ path: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentReadFile(path: path)) } func saveRuntimeFile(_ content: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentSaveFile(content: content)) } func uploadRuntimeFile(_ path: String, source: RuntimeUploadSource) { guard let remoteWorkspace else { source.close(); return }; remoteWorkspace.dispatch(intent: RemoteWorkspaceIntentUploadFile(path: path, source: source)) } + func createRuntimeFileEntry(_ name: String, directory: Bool) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentCreateFileEntry(name: name, directory: directory)) } + func renameRuntimeFileEntry(_ path: String, name: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentRenameFileEntry(path: path, name: name)) } + func deleteRuntimeFileEntry(_ path: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentDeleteFileEntry(path: path)) } + func uploadRuntimeFileEntry(_ name: String, source: RuntimeUploadSource) { guard let remoteWorkspace else { source.close(); return }; remoteWorkspace.dispatch(intent: RemoteWorkspaceIntentUploadFileEntry(name: name, source: source)) } func createRuntimeFile(_ path: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentCreateFile(path: path)) } func renameRuntimeFile(_ path: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentRenameFile(path: path)) } func deleteRuntimeFile() { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentDeleteFile.shared) } func createRuntimeDirectory(_ path: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentCreateDirectory(path: path)) } func resumeSessionStreams() { account.resumeSessionStreams() } func openRuntimeTerminal() { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentOpenTerminal.shared) } + func reopenRuntimeTerminal() { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentReopenTerminal.shared) } func writeRuntimeTerminal(_ data: String) { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentWriteTerminal(data: data)) } func closeRuntimeTerminal() { remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentCloseTerminal.shared) } @@ -490,6 +504,10 @@ final class MobileCoreAdapter { ) } + func retryRemoteDownload() { + remoteWorkspace?.dispatch(intent: RemoteWorkspaceIntentRetryDownload.shared) + } + func remoteDownloadSaved(reference: String) { remoteWorkspace?.dispatch( intent: RemoteWorkspaceIntentDownloadSaved(reference: reference) @@ -610,8 +628,17 @@ final class MobileCoreAdapter { let sessionFlow = SkieSwiftStateFlow(sessionStore.state) onRemoteState?(sessionFlow.value, targetKey, boundEpoch) remoteObservations.append(Task { [weak self] in + var lastHealthSignature = "" for await state in sessionFlow { guard !Task.isCancelled else { return } + #if DEBUG + let ready = state as? RemoteSessionUiStateReady + let signature = "ready=\(ready != nil) busy=\(ready?.busy ?? false) timeline=\(ready?.timeline != nil)" + if signature != lastHealthSignature { + self?.log.info("Remote health eligibility: \(signature, privacy: .public) epoch=\(boundEpoch)") + lastHealthSignature = signature + } + #endif self?.onRemoteState?(state, targetKey, boundEpoch) } }) @@ -621,6 +648,9 @@ final class MobileCoreAdapter { remoteObservations.append(Task { [weak self] in for await phase in connectionFlow { guard !Task.isCancelled else { return } + #if DEBUG + self?.log.info("Remote health phase: \(phase.name, privacy: .public) epoch=\(boundEpoch)") + #endif self?.onRemoteConnectionPhase?(phase, targetKey, boundEpoch) } }) @@ -708,3 +738,12 @@ final class IOSRuntimeUploadSource: RuntimeUploadSource { } func close() { try? handle.close(); if scoped { url.stopAccessingSecurityScopedResource() } } } + +/// CoreLog's contract excludes credentials and message bodies; the native sink +/// makes request/stream failure categories available in device diagnostics. +private final class MobileCoreLog: NSObject, CoreLog { + private let logger = Logger(subsystem: "com.openbitfun.mobile.ios", category: "remote-core") + func info(message: String) { logger.info("\(message, privacy: .public)") } + func warn(message: String) { logger.warning("\(message, privacy: .public)") } + func error(message: String) { logger.error("\(message, privacy: .public)") } +} diff --git a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileLocalization.swift b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileLocalization.swift index 39ef8c09d7..557da6d5aa 100644 --- a/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileLocalization.swift +++ b/src/apps/mobile/ios/OpenBitFun/Infrastructure/MobileLocalization.swift @@ -26,12 +26,10 @@ enum MobileLocalization { } static func text(_ key: String, language: MobileLanguage) -> String { - // The catalog's source language is Simplified Chinese and UI call sites - // use those source strings as stable keys. Asking Foundation to resolve - // an untranslated source key with an English development region can - // still fall through to the English localization, so keep the source - // language explicit instead of relying on Bundle fallback order. - if language == .simplifiedChinese { return key } + // Resolve only the selected language bundle. Returning every source key + // unchanged loses translated English keys added by remote features. + // An explicit bundle plus key fallback also avoids English fallback for + // untranslated Chinese source strings. guard let path = Bundle.main.path(forResource: language.rawValue, ofType: "lproj"), let bundle = Bundle(path: path) else { return key } return bundle.localizedString(forKey: key, value: key, table: nil) diff --git a/src/apps/mobile/ios/OpenBitFun/Presentation/Models/MobilePresentationModels.swift b/src/apps/mobile/ios/OpenBitFun/Presentation/Models/MobilePresentationModels.swift index 84de9ec2ca..0a6e4a891e 100644 --- a/src/apps/mobile/ios/OpenBitFun/Presentation/Models/MobilePresentationModels.swift +++ b/src/apps/mobile/ios/OpenBitFun/Presentation/Models/MobilePresentationModels.swift @@ -349,6 +349,12 @@ struct MobilePendingDownload: Identifiable, Equatable { } } +struct MobileWorkspaceScope: Equatable { + let path: String + let remoteConnectionId: String? + let remoteSshHost: String? +} + struct ChatSession: Identifiable, Equatable { let id: String var title: String @@ -358,6 +364,7 @@ struct ChatSession: Identifiable, Equatable { var agentType: String = "general_chat" var workspacePath: String? var workspaceName: String? + var workspaceScope: MobileWorkspaceScope? = nil var deviceKey: String? = nil var createdAt: String = "" var messageCount: Int = 0 @@ -376,6 +383,8 @@ struct PendingDirectoryRemoteDraft { let rawDeviceKey: String let workspacePath: String let normalizedWorkspacePath: String + var remoteConnectionId: String? = nil + var remoteSshHost: String? = nil var agentType: String = "code" let epoch: UInt64 var selectionRequested: Bool @@ -397,10 +406,12 @@ struct MobileDeviceDirectoryEntry: Identifiable, Equatable { let error: String? let workspaces: [MobileWorkspaceGroup] let sessions: [ChatSession] + var catalogSource: String? = nil + var recentWorkspaces: [MobileWorkspaceGroup]? = nil } struct MobileWorkspaceGroup: Identifiable, Equatable { - var id: String { (deviceKey ?? "") + ":" + (remoteConnectionId ?? "") + ":" + path } + var id: String { [deviceKey ?? "", remoteConnectionId ?? "", remoteSshHost ?? "", path].map { "\($0.utf8.count):\($0)" }.joined() } let path: String let name: String let selected: Bool @@ -409,6 +420,7 @@ struct MobileWorkspaceGroup: Identifiable, Equatable { var directoryExpanded = false var directoryStatus = "IDLE" var remoteConnectionId: String? = nil + var remoteSshHost: String? = nil } enum MobileSessionListSectionKind: Equatable { diff --git a/src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/Contents.json b/src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/Contents.json new file mode 100644 index 0000000000..53fcbac75b --- /dev/null +++ b/src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images": [ + { + "filename": "approval_shield.png", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/approval_shield.png b/src/apps/mobile/ios/OpenBitFun/Resources.xcassets/ApprovalShield.imageset/approval_shield.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb2022901ab1225e7d7ed273f97e49423c609 GIT binary patch literal 1324 zcmV+{1=IS8P)g&UE5TM>2u2i3TS2M_VzG7Q0~boG7}AGSORLo*-^|3= zbpDyioquj-Zsx!bfh6BK|C#?e_xmo;ph1Iv24BT_6bw-&JBKkd+jefj#pq{Hv5ty^U6>O!!2t^H zLcf8dxQ2p{x;ExaqdwpWR-iA8QM^gPsT>`jq+)H{CUcl%G2fu#YSamA;Q|Wor(!n+ z|KuDI`zW~bzZe}Xr(%E3|22-C6x?gvs1g`pA&0S)0dA$>NebSl;P@=2%imOV*up?! zY{AytCP!u&_c#?#gqk943wvh>aTXO1$0E94Y@^P6Z=J~GRNPL%4+YmP(>hMU2J1sP z=5%m*c)~5dz#gOG8IIxn+}te;Q1KiUzkA@{Wqm1QP0@&gnH9K7|@D{)BQ;dqyg!rQ#7?gp=pbRtyWuP%A1C2o$Xbj3gV^9VfgZkbeX)dd&XkK^p{R=a}pNS+j z$RUi)PcrDFKN4|#<}{3pP>+l@mia;s;pa=qB+F27SXzxk`%nHz6nh;cYfy1eaGPU_ zec_Kp9Z8pMJc4>;(#VET zitijFXq}JvA*fj7Ooi*JDxJ3?Kv7<5kgsS!O_wPh5nanAYa_M!LM z2;&~ZVk+KE<(}g;F+^CWmHq^+{Yt?@KI$XFx{&}6gfY`OP1Aj-0%#=%*(1rG=x278 z>Y8T+x7b#lgXe^4LY2U1i*?<7W_DnYEv&PQ{c+CN6ZZG0A}Cl!#h1A{5_RTt26!;3 zl+xFa zk>8|ln-%}wj5>nv;2bI*qv99u#zgDg;9Qhmc?Ye6**a6CLh$q>!^Wa7=_(0E#kr4D z@naVw#>{=HFO8WAws3ZMf3j~*NH5T}y84|Aa8Dib07^l4Ww0+Lh`v2xBHq0|4c~ze iV`(|84H`5+HSjM~GsYa4Z9J&}0000= 2, - "preview and download production entries remain closed after token expiry clears remote selection" + expectCallBeforeMutation( + in: filePreviewSource, function: "func openRemoteFile(", + call: "guard surface == .remote, remoteSessionSelected", + mutation: "adapter.openRemoteFile(", + message: "chat file preview remains closed after remote selection expires" + ) + expectCallBeforeMutation( + in: filePreviewSource, function: "func downloadRemoteFile(", + call: "guard remoteSessionSelected", mutation: "beginRemoteDownload(", + message: "chat download requires a selected session" ) + expectCallBeforeMutation( + in: filePreviewSource, function: "private func beginRemoteDownload(", + call: "guard surface == .remote, remoteConnected", mutation: "coreAdapter?.downloadRemoteFile(", + message: "both device-tool and chat downloads require a connected remote authority" + ) + } private static func readSource(_ url: URL) -> String { diff --git a/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/ConversationModels.kt b/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/ConversationModels.kt index 2a4dc84e2a..4842d36b33 100644 --- a/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/ConversationModels.kt +++ b/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/ConversationModels.kt @@ -47,7 +47,12 @@ public data class RemoteSession public constructor( public val messageCount: Int, public val workspacePath: String?, public val workspaceName: String?, -) + public val workspaceIdentity: RemoteWorkspaceIdentity?, +) { + public constructor(id: String, title: String, agentType: String, status: String, updatedAt: String, + createdAt: String, messageCount: Int, workspacePath: String?, workspaceName: String?) : + this(id, title, agentType, status, updatedAt, createdAt, messageCount, workspacePath, workspaceName, null) +} public data class ChatSessionCursor public constructor( public val pollVersion: Int, diff --git a/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentity.kt b/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentity.kt new file mode 100644 index 0000000000..293140062d --- /dev/null +++ b/src/apps/mobile/shared/core-domain/src/commonMain/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentity.kt @@ -0,0 +1,30 @@ +package com.openbitfun.mobile.core.domain + +/** A path belongs to the serving runtime's saved connection, not the phone. */ +public data class RemoteWorkspaceIdentity public constructor( + public val path: String, + public val remoteConnectionId: String?, + public val remoteSshHost: String?, +) { + public val key: String get() = listOf(remoteConnectionId.orEmpty(), remoteSshHost.orEmpty(), normalizedPath(path)) + .joinToString("") { "${it.length}:$it" } + + public fun matches(other: RemoteWorkspaceIdentity): Boolean = key == other.key + + public companion object { + private fun normalizedPath(path: String): String = path.trim().let { it.trimEnd('/').ifEmpty { it } } + } +} + +public fun RecentWorkspace.identity(): RemoteWorkspaceIdentity = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) + +/** Old cache rows have no provenance: only an unambiguous local root can own them. */ +public fun RemoteSession.belongsTo(workspace: RemoteWorkspaceIdentity, catalog: List): Boolean { + workspaceIdentity?.let { return it.matches(workspace) } + if (!workspace.remoteConnectionId.isNullOrEmpty() || !workspace.remoteSshHost.isNullOrEmpty()) return false + val local = RemoteWorkspaceIdentity(workspacePath.orEmpty(), null, null) + return local.matches(workspace) && catalog.none { + RemoteWorkspaceIdentity(it.path, null, null).matches(workspace) && + (!it.remoteConnectionId.isNullOrEmpty() || !it.remoteSshHost.isNullOrEmpty()) + } +} diff --git a/src/apps/mobile/shared/core-domain/src/commonTest/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentityTest.kt b/src/apps/mobile/shared/core-domain/src/commonTest/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentityTest.kt new file mode 100644 index 0000000000..f314188660 --- /dev/null +++ b/src/apps/mobile/shared/core-domain/src/commonTest/kotlin/com/openbitfun/mobile/core/domain/RemoteWorkspaceIdentityTest.kt @@ -0,0 +1,37 @@ +package com.openbitfun.mobile.core.domain + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.assertNotEquals + +class RemoteWorkspaceIdentityTest { + private val local = RemoteWorkspaceIdentity("/repo", null, null) + private val ssh = RemoteWorkspaceIdentity("/repo", "saved", "host") + private val legacy = RemoteSession("s", "Session", "code", "idle", "", "", 0, "/repo/", null) + + @Test + fun onlyUnambiguousLocalCatalogCanAdoptAnOldUnscopedSession() { + assertTrue(legacy.belongsTo(local, listOf(local))) + assertFalse(legacy.belongsTo(ssh, listOf(ssh))) + assertFalse(legacy.belongsTo(local, listOf(local, ssh))) + assertFalse(legacy.copy(workspacePath = null).belongsTo(local, listOf(local))) + } + + @Test + fun explicitOwnershipSurvivesSamePathAcrossConnections() { + val session = legacy.copy(workspaceIdentity = ssh) + assertTrue(session.belongsTo(ssh, listOf(local, ssh))) + assertFalse(session.belongsTo(local, listOf(local, ssh))) + assertFalse(session.belongsTo(ssh.copy(remoteConnectionId = "other"), listOf(ssh))) + assertTrue(ssh.matches(ssh.copy(path = "/repo/"))) + } + + @Test + fun identityKeysCannotCollideAcrossEmbeddedSeparators() { + assertNotEquals(RemoteWorkspaceIdentity("/c", "a:b", "c").key, + RemoteWorkspaceIdentity("/c", "a", "b:c").key) + assertNotEquals(RemoteWorkspaceIdentity("/", null, null).key, + RemoteWorkspaceIdentity("", null, null).key) + } +} diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/account/AccountStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/account/AccountStore.kt index 6a9119e38d..dda6fbeeda 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/account/AccountStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/account/AccountStore.kt @@ -19,6 +19,9 @@ import com.openbitfun.mobile.core.transport.TransportLog import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.isActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.collect @@ -95,6 +98,7 @@ public class AccountStore internal constructor( private var profileAttempt: Pair? = null /** Latest account membership snapshot, used to authorize explicit device stores. */ private var controllableDevices: List = emptyList() + private var pendingInitialDeviceSelection = false public fun resumeSessionStreams() { backend.resumeSessionStreams() } @@ -123,7 +127,7 @@ public class AccountStore internal constructor( public fun createSessionStore(scope: CoroutineScope): RemoteSessionStore? { val current = session ?: return null - val target = current.targetDeviceId?.takeIf(String::isNotBlank) ?: return null + val target = current.targetDeviceId?.let(::authorizedDeviceId) ?: return null return RemoteSessionStore.create( scope, backend.transport(current, target), @@ -134,7 +138,7 @@ public class AccountStore internal constructor( public fun createWorkspaceStore(scope: CoroutineScope): RemoteWorkspaceStore? { val current = session ?: return null - val target = current.targetDeviceId?.takeIf(String::isNotBlank) ?: return null + val target = current.targetDeviceId?.let(::authorizedDeviceId) ?: return null return RemoteWorkspaceStore.create( scope, backend.transport(current, target), @@ -196,6 +200,7 @@ public class AccountStore internal constructor( } private fun restore() { + pendingInitialDeviceSelection = false work?.cancel() _state.value = AccountUiState.Restoring work = scope.launch { @@ -229,31 +234,25 @@ public class AccountStore internal constructor( } session = restored selectedRelayUrl = restored.relayUrl - try { - publishReady(restored, backend.listDevices(restored, deviceId)) - } catch (cancelled: CancellationException) { - throw cancelled - } catch (error: CloudAccountException) { - if (error.failure == CloudAccountFailure.AUTHENTICATION) { - expireSession(error.failure.toUiReason(), AccountFailureStage.DEVICE_LIST) - } else { - _state.value = AccountUiState.Failed( - error.failure.toUiReason(), - true, - AccountFailureStage.DEVICE_LIST, - ) - } - } catch (_: Throwable) { - _state.value = AccountUiState.Failed( - AccountFailureReason.NETWORK, - true, - AccountFailureStage.DEVICE_LIST, - ) - } + // Local credential restoration and remote directory availability are + // separate facts. Publish identity before any network wait, but grant + // no target authority until an authenticated membership snapshot arrives. + publishReady(restored, emptyList(), selectionConfirmed = false) + refreshDevices() } } + /** A directory outage is not an authentication failure or device authority. */ + private fun publishDirectoryFailure(restored: AccountSessionData, reason: AccountFailureReason) { + publishReady(restored, emptyList()) + val ready = _state.value as AccountUiState.Ready + _state.value = ready.copy(refreshFailure = reason, selectedDeviceId = null, selectedDeviceName = null) + // The saved target stays in session and is restored after the relay has + // supplied its device directory. No new transport is authorized here. + } + private fun login() { + pendingInitialDeviceSelection = false work?.cancel() _state.value = AccountUiState.SigningIn work = scope.launch { @@ -266,20 +265,23 @@ public class AccountStore internal constructor( failLogin(AccountFailureReason.SECURE_STORAGE, AccountFailureStage.SECURE_STORAGE) return@launch } + val loginContext = currentCoroutineContext() val loggedIn = try { backend.login( selectedRelayUrl, deviceId, deviceName, deviceSecret, - { url -> _state.value = AccountUiState.Authorizing(url) }, + { url -> if (loginContext.isActive) _state.value = AccountUiState.Authorizing(url) }, ) } catch (cancelled: CancellationException) { throw cancelled } catch (error: CloudAccountException) { + currentCoroutineContext().ensureActive() failLogin(error.failure.toUiReason(), AccountFailureStage.AUTHENTICATION) return@launch } catch (_: Throwable) { + currentCoroutineContext().ensureActive() // Live transport failures are normalized by CloudAccountClient. // An untyped failure here is therefore a crypto/protocol failure, // never evidence that secure storage was involved. @@ -289,9 +291,11 @@ public class AccountStore internal constructor( deviceSecret.fill(0) } + currentCoroutineContext().ensureActive() controllableDevices = emptyList() if (!persistLogin(loggedIn)) return@launch session = loggedIn + pendingInitialDeviceSelection = true val devices = loadDevices(loggedIn) ?: return@launch @@ -305,31 +309,31 @@ public class AccountStore internal constructor( ) if (!persistLogin(selected)) return@launch session = selected + pendingInitialDeviceSelection = false publishReady(selected, devices) } } private suspend fun loadDevices(current: AccountSessionData): List? = try { - backend.listDevices(current, deviceId) + backend.listDevices(current, deviceId).also { currentCoroutineContext().ensureActive() } } catch (cancelled: CancellationException) { throw cancelled } catch (error: CloudAccountException) { - _state.value = AccountUiState.Failed( - error.failure.toUiReason(), - true, - AccountFailureStage.DEVICE_LIST, - ) + currentCoroutineContext().ensureActive() + if (error.failure == CloudAccountFailure.AUTHENTICATION) { + expireSession(error.failure.toUiReason(), AccountFailureStage.DEVICE_LIST) + } else { + publishDirectoryFailure(current, error.failure.toUiReason()) + } null } catch (_: Throwable) { - _state.value = AccountUiState.Failed( - AccountFailureReason.NETWORK, - true, - AccountFailureStage.DEVICE_LIST, - ) + currentCoroutineContext().ensureActive() + publishDirectoryFailure(current, AccountFailureReason.NETWORK) null } private fun retryFailedStage() { + if (_state.value is AccountUiState.Ready) { refreshDevices(); return } val failed = _state.value as? AccountUiState.Failed ?: return val current = session ?: return if (!failed.canRetry || failed.stage != AccountFailureStage.DEVICE_LIST) return @@ -348,6 +352,7 @@ public class AccountStore internal constructor( // This retry only refreshes the volatile device-list projection. The // authenticated bytes saved before the failed list request stay exact. session = selected + pendingInitialDeviceSelection = false publishReady(selected, devices) } } @@ -414,23 +419,44 @@ public class AccountStore internal constructor( work = scope.launch { try { val devices = backend.listDevices(current, deviceId) - if (session?.token == current.token && session?.relayUrl == current.relayUrl) publishReady(session!!, devices) + currentCoroutineContext().ensureActive() + if (session?.token == current.token && session?.relayUrl == current.relayUrl) { + val active = session!! + val selected = if (pendingInitialDeviceSelection) { + val preferred = AccountDevicePolicy.preferredTarget(devices, deviceId) + active.copy(targetDeviceId = preferred?.id, targetDeviceName = preferred?.name) + } else active + pendingInitialDeviceSelection = false + session = selected + publishReady(selected, devices) + } } catch (cancelled: CancellationException) { throw cancelled } catch (error: CloudAccountException) { + currentCoroutineContext().ensureActive() if (error.failure == CloudAccountFailure.AUTHENTICATION) { expireSession(error.failure.toUiReason(), AccountFailureStage.DEVICE_LIST) } else { - _state.value = ready.copy(refreshing = false, refreshFailure = error.failure.toUiReason()) + publishRefreshFailure(current, error.failure.toUiReason()) } } catch (_: Throwable) { - _state.value = ready.copy(refreshing = false, refreshFailure = AccountFailureReason.NETWORK) + currentCoroutineContext().ensureActive() + publishRefreshFailure(current, AccountFailureReason.NETWORK) } finally { - if (directoryDirty && session?.token == current.token) { directoryDirty = false; refreshDevices() } + if (currentCoroutineContext().isActive && directoryDirty && session?.token == current.token && session?.relayUrl == current.relayUrl) { directoryDirty = false; refreshDevices() } } } } + private fun publishRefreshFailure(requestSession: AccountSessionData, reason: AccountFailureReason) { + val active = session ?: return + if (active.token != requestSession.token || active.relayUrl != requestSession.relayUrl) return + val latest = _state.value as? AccountUiState.Ready ?: return + // Selection/profile changes made during the request belong to the current + // UI. A failed directory read has no authority to restore an old snapshot. + _state.value = latest.copy(refreshing = false, refreshFailure = reason) + } + private fun logout() { closeCatalogs() directoryWork?.cancel(); directoryIdentity = null @@ -471,7 +497,7 @@ public class AccountStore internal constructor( * filter cannot be forgotten by a caller โ€” or applied twice with two * different answers on two platforms. */ - private fun publishReady(current: AccountSessionData, devices: List) { + private fun publishReady(current: AccountSessionData, devices: List, selectionConfirmed: Boolean = true) { val directoryKey = current.relayUrl to current.token if (directoryIdentity != directoryKey) { directoryWork?.cancel(); directoryIdentity = directoryKey @@ -487,8 +513,8 @@ public class AccountStore internal constructor( relayUrl = current.relayUrl, username = displayedProfile?.takeIf { it.userId == current.userId }?.username ?: current.username, devices = controllableDevices, - selectedDeviceId = current.targetDeviceId, - selectedDeviceName = current.targetDeviceName, + selectedDeviceId = current.targetDeviceId.takeIf { selectionConfirmed }, + selectedDeviceName = current.targetDeviceName.takeIf { selectionConfirmed }, ).copy(avatarUrl = displayedProfile?.takeIf { it.userId == current.userId }?.avatarUrl) enrichProfile(current) } diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStore.kt index a48ffc31f0..739ac34a6c 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStore.kt @@ -1,5 +1,9 @@ package com.openbitfun.mobile.core.feature.directory +import com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity +import com.openbitfun.mobile.core.domain.identity +import com.openbitfun.mobile.core.domain.belongsTo + import com.openbitfun.mobile.core.domain.RemoteSession import com.openbitfun.mobile.core.domain.RecentWorkspace import com.openbitfun.mobile.core.feature.account.AccountStore @@ -45,10 +49,10 @@ public class DeviceDirectoryStore internal constructor( is DeviceDirectoryIntent.Retry -> retry(intent.deviceId) is DeviceDirectoryIntent.SetWorkspaceExpanded -> setWorkspaceExpanded( intent.deviceId, - intent.path, + RemoteWorkspaceIdentity(intent.path, intent.remoteConnectionId, intent.remoteSshHost), intent.expanded, ) - is DeviceDirectoryIntent.RetryWorkspace -> retryWorkspace(intent.deviceId, intent.path) + is DeviceDirectoryIntent.RetryWorkspace -> retryWorkspace(intent.deviceId, RemoteWorkspaceIdentity(intent.path, intent.remoteConnectionId, intent.remoteSshHost)) DeviceDirectoryIntent.Stop -> stop() } } @@ -220,49 +224,49 @@ public class DeviceDirectoryStore internal constructor( startLoad(id, slot, expanded) } - private fun setWorkspaceExpanded(deviceId: String, path: String, expanded: Boolean) { + private fun setWorkspaceExpanded(deviceId: String, identity: RemoteWorkspaceIdentity, expanded: Boolean) { val id = deviceId.trim() - val normalizedPath = normalizeWorkspacePath(path) + val normalizedPath = normalizeWorkspacePath(identity.path) val entry = devices[id] ?: return if (normalizedPath.isEmpty()) return - updateWorkspaceState(id, normalizedPath) { it.copy(expanded = expanded) } + updateWorkspaceState(id, identity) { it.copy(expanded = expanded) } if (!expanded || !entry.online) return - val state = devices[id]?.workspace(normalizedPath) - if (state?.status != WorkspaceDirectoryStatus.READY) loadWorkspaceSessions(id, normalizedPath, false) + val state = devices[id]?.workspace(identity.path, identity.remoteConnectionId, identity.remoteSshHost) + if (state?.status != WorkspaceDirectoryStatus.READY) loadWorkspaceSessions(id, identity, false) } - private fun retryWorkspace(deviceId: String, path: String) { + private fun retryWorkspace(deviceId: String, identity: RemoteWorkspaceIdentity) { val id = deviceId.trim() - val normalizedPath = normalizeWorkspacePath(path) + val normalizedPath = normalizeWorkspacePath(identity.path) val entry = devices[id] ?: return if (normalizedPath.isEmpty() || !entry.online) return - updateWorkspaceState(id, normalizedPath) { it.copy(expanded = true) } - loadWorkspaceSessions(id, normalizedPath, true) + updateWorkspaceState(id, identity) { it.copy(expanded = true) } + loadWorkspaceSessions(id, identity, true) } - private fun loadWorkspaceSessions(deviceId: String, path: String, force: Boolean) { - val key = workspaceKey(deviceId, path) + private fun loadWorkspaceSessions(deviceId: String, identity: RemoteWorkspaceIdentity, force: Boolean) { + val key = workspaceKey(deviceId, identity) if (workspaceLoads[key]?.isActive == true) return val entry = devices[deviceId] ?: return if (!entry.online) return - val existing = entry.workspace(path) + val existing = entry.workspace(identity.path, identity.remoteConnectionId, identity.remoteSshHost) if (!force && existing?.status == WorkspaceDirectoryStatus.READY) return val slot = slotFor(deviceId) ?: run { - updateWorkspaceState(deviceId, path) { it.copy(status = WorkspaceDirectoryStatus.FAILED) } + updateWorkspaceState(deviceId, identity) { it.copy(status = WorkspaceDirectoryStatus.FAILED) } return } val generation = nextWorkspaceGeneration(key) - updateWorkspaceState(deviceId, path) { it.copy(status = WorkspaceDirectoryStatus.LOADING) } + updateWorkspaceState(deviceId, identity) { it.copy(status = WorkspaceDirectoryStatus.LOADING) } val job = scope.launch { try { - val loaded = slot.sessionStore.sessionsForWorkspace(path) + val loaded = slot.sessionStore.sessionsForWorkspace(identity.path, identity.remoteConnectionId, identity.remoteSshHost) if (!isCurrentWorkspace(key, generation) || devices[deviceId]?.online != true) return@launch val current = devices[deviceId] ?: return@launch - val merged = replaceWorkspaceSessions(current.sessions, path, loaded) + val merged = replaceWorkspaceSessions(current.sessions, identity, loaded) slot.sessionStore.persistDirectorySessions(merged) devices[deviceId] = current.copy( sessions = merged, - workspaceDirectory = updateWorkspaceList(current.workspaceDirectory, path) { + workspaceDirectory = updateWorkspaceList(current.workspaceDirectory, identity) { it.copy(status = WorkspaceDirectoryStatus.READY) }, ) @@ -271,7 +275,7 @@ public class DeviceDirectoryStore internal constructor( throw cancelled } catch (_: Throwable) { if (isCurrentWorkspace(key, generation)) { - updateWorkspaceState(deviceId, path) { it.copy(status = WorkspaceDirectoryStatus.FAILED) } + updateWorkspaceState(deviceId, identity) { it.copy(status = WorkspaceDirectoryStatus.FAILED) } } } finally { if (workspaceLoads[key] === coroutineContext[Job]) workspaceLoads.remove(key) @@ -311,13 +315,14 @@ public class DeviceDirectoryStore internal constructor( if (cachedWorkspaces.isEmpty() && cachedSessions.isEmpty()) return val sessions = if (cachedSessions.isEmpty()) current.sessions else cachedSessions val workspaces = when { + current.catalogSource != null -> current.workspaces cachedWorkspaces.isNotEmpty() -> cachedWorkspaces current.workspaces.isNotEmpty() -> current.workspaces - else -> workspacesFromSessions(sessions) + else -> emptyList() } val workspaceDirectory = syncWorkspaceDirectory(current.workspaceDirectory, workspaces).map { workspace -> if (sessions.any { session -> - normalizeWorkspacePath(session.workspacePath.orEmpty()) == normalizeWorkspacePath(workspace.path) + session.belongsTo(workspace.identity, workspaces.map { it.identity() }) } ) { workspace.copy(status = WorkspaceDirectoryStatus.READY) @@ -360,7 +365,8 @@ public class DeviceDirectoryStore internal constructor( private suspend fun runLoad(id: String, slot: DeviceSlot, generation: Long) { if (!isCurrent(id, generation)) return try { - val workspaces = slot.workspaceStore.directoryCatalog() + val catalog = slot.workspaceStore.directoryCatalog() + val workspaces = catalog.workspaces if (!isCurrent(id, generation)) return val current = devices[id] ?: return // A device that went offline while its request was in flight stays @@ -370,6 +376,8 @@ public class DeviceDirectoryStore internal constructor( status = DeviceDirectoryStatus.READY, error = null, workspaces = workspaces, + catalogSource = catalog.source, + recentWorkspaces = catalog.recentWorkspaces, workspaceDirectory = syncWorkspaceDirectory(current.workspaceDirectory, workspaces), ) publish() @@ -388,77 +396,40 @@ public class DeviceDirectoryStore internal constructor( } private fun replaceWorkspaceSessions( - sessions: List, - path: String, - replacement: List, + sessions: List, identity: RemoteWorkspaceIdentity, replacement: List, ): List { - val normalizedPath = normalizeWorkspacePath(path) val replacementIds = replacement.mapTo(mutableSetOf()) { it.id } - val kept = sessions.filter { session -> - session.id !in replacementIds && normalizeWorkspacePath(session.workspacePath.orEmpty()) != normalizedPath - } - return replacement + kept - } - - /** Keeps the pre-workspace-cache upgrade path useful by rebuilding its minimum catalog. */ - private fun workspacesFromSessions(sessions: List): List { - val inferred = linkedMapOf() - for (session in sessions) { - val path = session.workspacePath.orEmpty().trim() - val normalized = normalizeWorkspacePath(path) - if (normalized.isEmpty() || normalized in inferred) continue - inferred[normalized] = RecentWorkspace( - path = path, - name = session.workspaceName?.takeIf(String::isNotBlank) - ?: normalized.substringAfterLast('/').ifEmpty { normalized }, - lastOpened = session.updatedAt, - kind = "", - ) - } - return inferred.values.toList() + return replacement + sessions.filter { it.id !in replacementIds && it.workspaceIdentity?.matches(identity) != true } } private fun syncWorkspaceDirectory( - current: List, - workspaces: List, + current: List, workspaces: List, ): List = workspaces.map { workspace -> - current.firstOrNull { normalizeWorkspacePath(it.path) == normalizeWorkspacePath(workspace.path) } - ?.copy(path = workspace.path) - ?: WorkspaceDirectoryEntry(workspace.path, false, WorkspaceDirectoryStatus.IDLE) + current.firstOrNull { it.identity.matches(workspace.identity()) }?.copy(path = workspace.path) + ?: WorkspaceDirectoryEntry(workspace.path, false, WorkspaceDirectoryStatus.IDLE, workspace.remoteConnectionId, workspace.remoteSshHost) } private fun updateWorkspaceList( - current: List, - path: String, + current: List, identity: RemoteWorkspaceIdentity, transform: (WorkspaceDirectoryEntry) -> WorkspaceDirectoryEntry, ): List { var found = false val updated = current.map { entry -> - if (normalizeWorkspacePath(entry.path) == normalizeWorkspacePath(path)) { - found = true - transform(entry) - } else { - entry - } + if (entry.identity.matches(identity)) { found = true; transform(entry) } else entry }.toMutableList() - if (!found) updated += transform(WorkspaceDirectoryEntry(path, false, WorkspaceDirectoryStatus.IDLE)) + if (!found) updated += transform(WorkspaceDirectoryEntry(identity.path, false, WorkspaceDirectoryStatus.IDLE, identity.remoteConnectionId, identity.remoteSshHost)) return updated } private fun updateWorkspaceState( - deviceId: String, - path: String, - transform: (WorkspaceDirectoryEntry) -> WorkspaceDirectoryEntry, + deviceId: String, identity: RemoteWorkspaceIdentity, transform: (WorkspaceDirectoryEntry) -> WorkspaceDirectoryEntry, ) { val current = devices[deviceId] ?: return - devices[deviceId] = current.copy( - workspaceDirectory = updateWorkspaceList(current.workspaceDirectory, path, transform), - ) + devices[deviceId] = current.copy(workspaceDirectory = updateWorkspaceList(current.workspaceDirectory, identity, transform)) publish() } - private fun workspaceKey(deviceId: String, path: String): String = - "$deviceId\u0001${normalizeWorkspacePath(path)}" + private fun workspaceKey(deviceId: String, identity: RemoteWorkspaceIdentity): String = "$deviceId\u0001${identity.key}" private fun normalizeWorkspacePath(path: String): String { val trimmed = path.trim() diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryUiState.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryUiState.kt index 16902ebbb6..e4e5a099ed 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryUiState.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryUiState.kt @@ -1,7 +1,11 @@ package com.openbitfun.mobile.core.feature.directory +import com.openbitfun.mobile.core.domain.identity +import com.openbitfun.mobile.core.domain.belongsTo +import com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity import com.openbitfun.mobile.core.domain.RecentWorkspace import com.openbitfun.mobile.core.domain.RemoteSession +import com.openbitfun.mobile.core.feature.workspace.WorkspaceCatalogSource /** * Where one device's directory entry is in its load lifecycle. @@ -35,7 +39,12 @@ public data class WorkspaceDirectoryEntry public constructor( public val path: String, public val expanded: Boolean, public val status: WorkspaceDirectoryStatus, -) + public val remoteConnectionId: String?, + public val remoteSshHost: String?, +) { + public constructor(path: String, expanded: Boolean, status: WorkspaceDirectoryStatus) : this(path, expanded, status, null, null) + public val identity: RemoteWorkspaceIdentity get() = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) +} /** Why a device's directory content cannot be shown. */ public enum class DeviceDirectoryFailure { @@ -69,7 +78,10 @@ public data class DeviceDirectoryEntry public constructor( public val workspaces: List, public val sessions: List, public val workspaceDirectory: List, + public val catalogSource: WorkspaceCatalogSource?, + public val recentWorkspaces: List, ) { + public constructor(deviceId: String, deviceName: String, online: Boolean, expanded: Boolean, status: DeviceDirectoryStatus, error: DeviceDirectoryFailure?, workspaces: List, sessions: List, workspaceDirectory: List) : this(deviceId, deviceName, online, expanded, status, error, workspaces, sessions, workspaceDirectory, null, workspaces) public constructor( deviceId: String, deviceName: String, @@ -81,9 +93,16 @@ public data class DeviceDirectoryEntry public constructor( sessions: List, ) : this(deviceId, deviceName, online, expanded, status, error, workspaces, sessions, emptyList()) - public fun workspace(path: String): WorkspaceDirectoryEntry? { - val normalized = normalizeWorkspacePath(path) - return workspaceDirectory.firstOrNull { normalizeWorkspacePath(it.path) == normalized } + public fun sessionsForWorkspace(path: String, remoteConnectionId: String?, remoteSshHost: String?): List { + val identity = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) + return sessions.filter { it.belongsTo(identity, workspaces.map { row -> row.identity() }) } + } + + public fun workspace(path: String): WorkspaceDirectoryEntry? = workspace(path, null, null) + + public fun workspace(path: String, remoteConnectionId: String?, remoteSshHost: String?): WorkspaceDirectoryEntry? { + val identity = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) + return workspaceDirectory.firstOrNull { it.identity.matches(identity) } } public companion object { @@ -155,12 +174,20 @@ public sealed interface DeviceDirectoryIntent { public val deviceId: String, public val path: String, public val expanded: Boolean, - ) : DeviceDirectoryIntent + public val remoteConnectionId: String?, + public val remoteSshHost: String?, + ) : DeviceDirectoryIntent { + public constructor(deviceId: String, path: String, expanded: Boolean) : this(deviceId, path, expanded, null, null) + } public data class RetryWorkspace public constructor( public val deviceId: String, public val path: String, - ) : DeviceDirectoryIntent + public val remoteConnectionId: String?, + public val remoteSshHost: String?, + ) : DeviceDirectoryIntent { + public constructor(deviceId: String, path: String) : this(deviceId, path, null, null) + } public data object Stop : DeviceDirectoryIntent } diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStore.kt index 028758d261..dcf86837af 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStore.kt @@ -13,8 +13,15 @@ public data class PermissionMailboxRequest public constructor( public data class PermissionMailboxUiState public constructor( public val requests: List, public val busy: Boolean, public val failed: Boolean, public val questions: List, + public val ownedToolIds: Set, ) { + public constructor(requests: List, busy: Boolean, failed: Boolean, questions: List) : + this(requests, busy, failed, questions, emptySet()) public constructor(requests: List, busy: Boolean, failed: Boolean) : this(requests, busy, failed, emptyList()) + + /** A transcript row may show details, but must not duplicate a mailbox action. */ + public fun ownsToolInteraction(toolId: String): Boolean = + toolId.isNotEmpty() && (toolId in ownedToolIds || questions.any { it.id == toolId } || requests.any { it.toolCallId == toolId }) } @Serializable private data class MailboxResult(override val resp: String? = null, override val message: String? = null, @@ -39,7 +46,7 @@ internal class PermissionMailboxStore(private val scope: CoroutineScope, private } private suspend fun invoke(command: String, args: JsonObject): JsonElement { val result = transport.send(RemoteCommand(cmd = "host_invoke", command = command, args = args)) - check(result.ok) { "Runtime permission request failed" } + check(result.ok && !result.isError) { "Runtime permission request failed" } return result.value } fun invalidate() { @@ -51,7 +58,21 @@ internal class PermissionMailboxStore(private val scope: CoroutineScope, private try { while (dirty && ticket == epoch) { dirty = false - val snapshot = invoke("get_session_interaction_mailbox", buildJsonObject { put("request", buildJsonObject { put("sessionId", selected) }) }).jsonObject + val snapshot = try { + invoke("get_session_interaction_mailbox", buildJsonObject { put("request", buildJsonObject { put("sessionId", selected) }) }).jsonObject + } catch (cancelled: CancellationException) { + throw cancelled + } catch (error: Throwable) { + if (ticket != epoch) return@launch + // A newer invalidation must survive an older request failure. + if (dirty) continue + throw error + } + // An interaction event may invalidate this snapshot while the RPC is + // in flight. Keep the last authoritative rows until the newer read + // completes, matching Harmony's mailbox version check. + if (ticket != epoch) return@launch + if (dirty) continue check(snapshot.getValue("sessionId").jsonPrimitive.content == selected) { "Interaction mailbox session mismatch" } val value = snapshot.getValue("permissions").jsonObject.getValue("requests").jsonArray val questions = snapshot.getValue("userQuestions").jsonObject.getValue("questions").jsonArray.map { it.jsonObject } @@ -65,7 +86,10 @@ internal class PermissionMailboxStore(private val scope: CoroutineScope, private it["toolCallId"]?.jsonPrimitive?.contentOrNull, (it["source"] as? JsonObject)?.get("identity")?.jsonPrimitive?.contentOrNull.orEmpty()) } - if (ticket == epoch) update(state.copy(requests = requests, questions = questions, failed = false)) + // Keep ownership after a successful reply removes the mailbox + // row: transcript completion may arrive later over another stream. + val ownedToolIds = state.ownedToolIds + questions.map { it.id } + requests.mapNotNull { it.toolCallId } + if (ticket == epoch) update(state.copy(requests = requests, questions = questions, failed = false, ownedToolIds = ownedToolIds)) } } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { if (ticket == epoch) update(state.copy(failed = true)) } @@ -73,27 +97,61 @@ internal class PermissionMailboxStore(private val scope: CoroutineScope, private } fun startQuestion(toolId: String) { val selected = session ?: return + if (state.questions.none { it.id == toolId }) return if (!interacting.add(toolId)) return val ticket = epoch scope.launch { if (ticket != epoch) return@launch - try { transport.send(RemoteCommand(cmd = "start_question_interaction", sessionId = selected, toolId = toolId)) } + try { + val result = transport.send(RemoteCommand(cmd = "start_question_interaction", sessionId = selected, toolId = toolId)) + check(!result.isError) { result.message ?: "Question interaction failed" } + if (ticket == epoch) invalidate() + } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { if (ticket == epoch) { interacting.remove(toolId); update(state.copy(failed = true)) } } } } + /** Returns false only for questions owned by the legacy transcript path. */ + fun answer(selected: String, toolId: String, answers: JsonObject): Boolean { + if (session != selected || state.questions.none { it.id == toolId }) return false + if (state.busy) return true + val ticket = epoch + update(state.copy(busy = true, failed = false)) + reply = scope.launch { + try { + val result = transport.send(RemoteCommand( + cmd = "answer_question", sessionId = selected, toolId = toolId, answers = answers)) + check(!result.isError) { "Question answer failed" } + if (ticket == epoch) { + invalidate() + refresh?.join() + } + } catch (cancelled: CancellationException) { throw cancelled } + catch (_: Throwable) { if (ticket == epoch) update(state.copy(failed = true)) } + finally { if (ticket == epoch) update(state.copy(busy = false)) } + } + return true + } + fun respond(requestId: String, approve: Boolean, updatedInput: String?) { if (state.busy || state.requests.none { it.requestId == requestId }) return val ticket = epoch update(state.copy(busy = true, failed = false)) reply = scope.launch { try { - val patch = updatedInput?.let { Json.parseToJsonElement(it) as? JsonObject ?: error("Input must be an object") } + val patch = if (approve) updatedInput?.let { + Json.parseToJsonElement(it) as? JsonObject ?: error("Input must be an object") + } else null invoke("respond_permission", buildJsonObject { put("request", buildJsonObject { put("requestId", requestId); put("reply", if (approve) "once" else "reject") if (approve && patch != null) put("updatedInput", patch) }) }) - if (ticket == epoch) invalidate() + if (ticket == epoch) { + invalidate() + // Keep the mutation busy until the authoritative mailbox refresh + // finishes, so stale request rows cannot be submitted again. + refresh?.join() + } } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { if (ticket == epoch) update(state.copy(failed = true)) } finally { if (ticket == epoch) update(state.copy(busy = false)) } diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentation.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentation.kt new file mode 100644 index 0000000000..cddb87886e --- /dev/null +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentation.kt @@ -0,0 +1,21 @@ +package com.openbitfun.mobile.core.feature.session + +/** Platform-neutral facts needed to select the remote landing-page entries. */ +public data class RecentSessionUiState public constructor( + public val id: String, + public val status: String, + public val updatedAt: String, + public val createdAt: String, +) + +public object RecentSessionsPresentation { + public fun sessionIds(sessions: List): List = sessions + .filter { it.id.isNotBlank() && it.status != "archived" } + .sortedByDescending { + SessionTimePresentation.timestampMs(it.updatedAt)?.takeIf { time -> time > 0 } + ?: SessionTimePresentation.timestampMs(it.createdAt) ?: 0 + } + .distinctBy { it.id } + .take(3) + .map { it.id } +} diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStore.kt index 8dfbe39c0c..d7b6ce07df 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStore.kt @@ -1,5 +1,11 @@ package com.openbitfun.mobile.core.feature.session +import com.openbitfun.mobile.core.transport.RelayTransportException +import com.openbitfun.mobile.core.transport.RelayFailure +import com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity +import com.openbitfun.mobile.core.domain.belongsTo +import com.openbitfun.mobile.core.persistence.PersistedWorkspaceIdentity + import com.openbitfun.mobile.core.feature.relay.HostCatalogNotice import com.openbitfun.mobile.core.domain.ChatSyncPhase @@ -29,6 +35,7 @@ import com.openbitfun.mobile.core.protocol.RemoteToolStatusResponse import com.openbitfun.mobile.core.protocol.RemoteCommand import com.openbitfun.mobile.core.protocol.RemotePermissionMode import com.openbitfun.mobile.core.protocol.RemoteModelCatalog +import com.openbitfun.mobile.core.protocol.isError import com.openbitfun.mobile.core.protocol.CommandStatusResponse import com.openbitfun.mobile.core.protocol.PermissionModeResponse import com.openbitfun.mobile.core.protocol.SetSessionModelResponse @@ -44,6 +51,7 @@ import com.openbitfun.mobile.core.transport.send import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceIntent import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceStore import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceUiState +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -95,6 +103,7 @@ public class RemoteSessionStore internal constructor( commitSessionPage(page) if (persistenceEnabled && before.query.isEmpty() && before.agentFilter == SessionAgentFilter.ALL) savePersistedSessions(page.sessions, page.hasMore) publishAuthorityReady(latest.copy(sessions = page.sessions, hasMore = page.hasMore)) + refreshModelCatalog(invalidated = true) markConnected() } } catch (cancelled: CancellationException) { throw cancelled } @@ -117,6 +126,9 @@ public class RemoteSessionStore internal constructor( private var activeCreateGeneration: Long? = null private var authorityRevision: Long = 0 private var workGeneration: Long = 0 + private var modelCatalogRefresh: Job? = null + private var modelCatalogDirty = false + private var permissionRefresh: Job? = null private val timelineStore = ChatTimelineStore() private val permissionMailbox = PermissionMailboxStore(scope, transport) { mailbox -> val ready = _state.value as? RemoteSessionUiState.Ready @@ -124,6 +136,8 @@ public class RemoteSessionStore internal constructor( } private var sessionUpdates: Job? = null private var work: Job? = null + private var historyWork: Job? = null + private var historyGeneration = 0L private var healthWork: Job? = null private var healthGeneration: Long = 0 private var modelCatalog: RemoteModelCatalog? = null @@ -148,6 +162,7 @@ public class RemoteSessionStore internal constructor( * from the UI โ€” the same shape as `RemoteSessionManager.workspace`. */ private var workspaceConnectionId: String? = null + private var workspaceSshHost: String? = null private var workspacePath: String = "" private var hostCapabilities: List = emptyList() @@ -162,8 +177,8 @@ public class RemoteSessionStore internal constructor( load(current?.query.orEmpty(), current?.agentFilter ?: SessionAgentFilter.ALL) RemoteSessionIntent.LoadMore -> loadMore() RemoteSessionIntent.LoadOlderMessages -> loadOlderMessages() - is RemoteSessionIntent.LoadWorkspaceSessions -> loadWorkspaceSessions(intent.path, false) - is RemoteSessionIntent.RetryWorkspaceSessions -> loadWorkspaceSessions(intent.path, true) + is RemoteSessionIntent.LoadWorkspaceSessions -> loadWorkspaceSessions(intent.path, false, intent.remoteConnectionId, intent.remoteSshHost) + is RemoteSessionIntent.RetryWorkspaceSessions -> loadWorkspaceSessions(intent.path, true, intent.remoteConnectionId, intent.remoteSshHost) is RemoteSessionIntent.Search -> load(intent.query, current?.agentFilter ?: SessionAgentFilter.ALL) is RemoteSessionIntent.SetAgentFilter -> load(current?.query.orEmpty(), intent.filter) @@ -171,7 +186,7 @@ public class RemoteSessionStore internal constructor( is RemoteSessionIntent.CreateSession -> createSession(intent, nextRequestId()) is RemoteSessionIntent.CreateSessionOperation -> createSession( RemoteSessionIntent.CreateSession( - intent.agentType, intent.title, intent.instruction, intent.modelId, intent.workspacePath, intent.remoteConnectionId, + intent.agentType, intent.title, intent.instruction, intent.modelId, intent.workspacePath, intent.remoteConnectionId, intent.remoteSshHost, ), intent.requestId.trim().ifEmpty { nextRequestId() }, ) @@ -344,13 +359,16 @@ public class RemoteSessionStore internal constructor( } /** Loads one disclosed workspace without changing the desktop's active workspace. */ - internal suspend fun sessionsForWorkspace(path: String): List { + internal suspend fun sessionsForWorkspace(path: String, remoteConnectionId: String? = null, remoteSshHost: String? = null): List { + val identity = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) val normalizedPath = path.trim() if (normalizedPath.isEmpty()) return emptyList() val response = transport.send( RemoteCommand( cmd = "list_sessions", workspacePath = normalizedPath, + remoteConnectionId = remoteConnectionId, + remoteSshHost = remoteSshHost, limit = DIRECTORY_WORKSPACE_PAGE_SIZE, offset = 0, ), @@ -359,12 +377,12 @@ public class RemoteSessionStore internal constructor( .map(RemoteResponseMapper::session) .filter { SessionAgentTypes.isMobileVisible(it.agentType) } .map { session -> - if (session.workspacePath.isNullOrBlank()) session.copy(workspacePath = normalizedPath) else session + session.copy(workspacePath = session.workspacePath?.takeIf { it.isNotBlank() } ?: normalizedPath, workspaceIdentity = identity) } val serverIds = server.mapTo(mutableSetOf()) { it.id } serverIds.forEach(locallyCreatedSessions::remove) val pending = locallyCreatedSessions.values.filter { session -> - session.id !in serverIds && session.workspacePath.orEmpty().trim() == normalizedPath + session.id !in serverIds && session.belongsTo(identity, listOf(identity)) } return pending + server } @@ -374,55 +392,57 @@ public class RemoteSessionStore internal constructor( savePersistedSessions(sessions, false) } - private fun loadWorkspaceSessions(path: String, force: Boolean) { + private fun loadWorkspaceSessions(path: String, force: Boolean, remoteConnectionId: String?, remoteSshHost: String?) { val normalizedPath = normalizeWorkspacePath(path) if (normalizedPath.isEmpty()) return - if (workspaceDirectoryJobs[normalizedPath]?.isActive == true) return - val existing = _workspaceDirectory.value.workspace(normalizedPath) + val identity = RemoteWorkspaceIdentity(normalizedPath, remoteConnectionId, remoteSshHost) + val key = identity.key + if (workspaceDirectoryJobs[key]?.isActive == true) return + val existing = _workspaceDirectory.value.workspace(normalizedPath, remoteConnectionId, remoteSshHost) if (!force && existing?.status == WorkspaceSessionDirectoryStatus.READY) return - val generation = (workspaceDirectoryGenerations[normalizedPath] ?: 0L) + 1L - workspaceDirectoryGenerations[normalizedPath] = generation - updateWorkspaceDirectory(normalizedPath) { + val generation = (workspaceDirectoryGenerations[key] ?: 0L) + 1L + workspaceDirectoryGenerations[key] = generation + updateWorkspaceDirectory(identity) { it.copy(status = WorkspaceSessionDirectoryStatus.LOADING) } val job = scope.launch { try { - val loaded = sessionsForWorkspace(normalizedPath) - if (workspaceDirectoryGenerations[normalizedPath] != generation) return@launch + val loaded = sessionsForWorkspace(normalizedPath, remoteConnectionId, remoteSshHost) + if (workspaceDirectoryGenerations[key] != generation) return@launch if (persistenceEnabled) { val cached = cachedSessions() - persistDirectorySessions(replaceWorkspaceSessions(cached, normalizedPath, loaded)) + persistDirectorySessions(replaceWorkspaceSessions(cached, identity, loaded)) } - updateWorkspaceDirectory(normalizedPath) { + updateWorkspaceDirectory(identity) { it.copy(status = WorkspaceSessionDirectoryStatus.READY, sessions = loaded) } } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { - if (workspaceDirectoryGenerations[normalizedPath] == generation) { - updateWorkspaceDirectory(normalizedPath) { + if (workspaceDirectoryGenerations[key] == generation) { + updateWorkspaceDirectory(identity) { it.copy(status = WorkspaceSessionDirectoryStatus.FAILED) } } } finally { - if (workspaceDirectoryJobs[normalizedPath] === coroutineContext[Job]) { - workspaceDirectoryJobs.remove(normalizedPath) + if (workspaceDirectoryJobs[key] === coroutineContext[Job]) { + workspaceDirectoryJobs.remove(key) } } } - workspaceDirectoryJobs[normalizedPath] = job - if (!job.isActive && workspaceDirectoryJobs[normalizedPath] === job) { - workspaceDirectoryJobs.remove(normalizedPath) + workspaceDirectoryJobs[key] = job + if (!job.isActive && workspaceDirectoryJobs[key] === job) { + workspaceDirectoryJobs.remove(key) } } private fun updateWorkspaceDirectory( - path: String, + identity: RemoteWorkspaceIdentity, transform: (WorkspaceSessionDirectoryEntry) -> WorkspaceSessionDirectoryEntry, ) { var found = false val entries = _workspaceDirectory.value.workspaces.map { entry -> - if (normalizeWorkspacePath(entry.path) == normalizeWorkspacePath(path)) { + if (entry.identity.matches(identity)) { found = true transform(entry) } else { @@ -431,7 +451,7 @@ public class RemoteSessionStore internal constructor( }.toMutableList() if (!found) { entries += transform( - WorkspaceSessionDirectoryEntry(path, WorkspaceSessionDirectoryStatus.IDLE, emptyList()), + WorkspaceSessionDirectoryEntry(identity.path, WorkspaceSessionDirectoryStatus.IDLE, emptyList(), identity.remoteConnectionId, identity.remoteSshHost), ) } _workspaceDirectory.value = WorkspaceSessionDirectoryUiState(entries) @@ -439,14 +459,13 @@ public class RemoteSessionStore internal constructor( private fun replaceWorkspaceSessions( sessions: List, - path: String, + identity: RemoteWorkspaceIdentity, replacement: List, ): List { - val normalizedPath = normalizeWorkspacePath(path) val replacementIds = replacement.mapTo(mutableSetOf()) { it.id } val retained = sessions.filter { session -> session.id !in replacementIds && - normalizeWorkspacePath(session.workspacePath.orEmpty()) != normalizedPath + !session.belongsTo(identity, _workspaceDirectory.value.workspaces.map { it.identity }) } return replacement + retained } @@ -508,6 +527,9 @@ public class RemoteSessionStore internal constructor( private fun beginWork(): Long { workGeneration += 1 work?.cancel() + historyWork?.cancel() + modelCatalogRefresh?.cancel() + permissionRefresh?.cancel() return workGeneration } @@ -577,18 +599,13 @@ public class RemoteSessionStore internal constructor( failKnown(RemoteSessionFailureReason.NO_WORKSPACE, current) return@launch } - // The catalog is independent of the authoritative workspace/session - // projection. Start both requests together so a slow catalog cannot - // add its latency to the session-list request (while still awaiting - // both before publishing the existing Ready contract). - val (page, catalog) = coroutineScope { - val pageRequest = async { listSessions(0, query, filter) } - val catalogRequest = async { loadModelCatalog(force = false) } - pageRequest.await() to catalogRequest.await() - } + // Catalog enrichment must not gate navigation or transcript loading. + // Keep it a child of this load so replacing/stopping the load cancels + // it, but publish the authoritative session page before awaiting it. + val catalogRequest = async { loadModelCatalog(force = false) } + val page = listSessions(0, query, filter) if (!isCurrentWork(generation)) return@launch commitSessionPage(page) - commitModelCatalog(catalog) if (persistenceEnabled && query.isEmpty() && filter == SessionAgentFilter.ALL) { savePersistedSessions(page.sessions, page.hasMore) } @@ -604,11 +621,19 @@ public class RemoteSessionStore internal constructor( agentFilter = filter, hasMore = page.hasMore, hasMoreMessages = current?.hasMoreMessages ?: false, - modelCatalog = catalog.catalog ?: current?.modelCatalog, - modelCatalogFailure = catalog.failure, + modelCatalog = modelCatalog ?: current?.modelCatalog, + modelCatalogFailure = modelCatalogFailure, draft = current?.draft ?: "", )) markConnected() + val catalog = catalogRequest.await() + if (!isCurrentWork(generation)) return@launch + commitModelCatalog(catalog) + val ready = _state.value as? RemoteSessionUiState.Ready ?: return@launch + _state.value = ready.copy( + modelCatalog = catalog.catalog ?: ready.modelCatalog, + modelCatalogFailure = catalog.failure, + ) } catch (cancelled: CancellationException) { throw cancelled } catch (error: Throwable) { @@ -662,12 +687,14 @@ public class RemoteSessionStore internal constructor( if (!isCurrentWork(operationToken)) return false workspacePath = (info.path ?: info.workspacePath).orEmpty().trim() workspaceConnectionId = info.remoteConnectionId + workspaceSshHost = info.remoteSshHost hostCapabilities = info.capabilities return workspacePath.isNotEmpty() && workspacePath != "/" } private suspend fun listSessions(offset: Int, query: String, filter: SessionAgentFilter, count: Int = PAGE_SIZE): SessionPage { val trimmedQuery = query.trim() + val identity = RemoteWorkspaceIdentity(workspacePath, workspaceConnectionId, workspaceSshHost) // `list_sessions` cannot apply either the mobile ACP visibility rule or // the agent tab. Pull from the start until there are enough visible rows // so an invisible server row never creates a short page or a dishonest @@ -678,8 +705,8 @@ public class RemoteSessionStore internal constructor( var hasMore = true val pageSize = if (filter == SessionAgentFilter.ALL) PAGE_SIZE else FILTER_PAGE_SIZE while (hasMore && filtered.size < targetCount) { - val response = sendListSessions(pageSize, pageOffset, trimmedQuery) - val sessions = response.sessions.map(RemoteResponseMapper::session) + val response = sendListSessions(pageSize, pageOffset, trimmedQuery, identity) + val sessions = response.sessions.map(RemoteResponseMapper::session).map { it.copy(workspaceIdentity = identity) } sessions.filterTo(filtered) { SessionAgentTypes.isMobileVisible(it.agentType) && filter.matches(it.agentType) } @@ -730,12 +757,12 @@ public class RemoteSessionStore internal constructor( * [force] re-requests even when a catalog is already cached, which is how a * settings retry reaches a catalog that a transient failure lost. */ - private suspend fun loadModelCatalog(force: Boolean): ModelCatalogLoadResult { + private suspend fun loadModelCatalog(force: Boolean, sessionId: String? = null): ModelCatalogLoadResult { if (!force) { modelCatalog?.let { return ModelCatalogLoadResult(it, null) } } return try { - val catalog = transport.send(RemoteCommand(cmd = "get_model_catalog")).catalog + val catalog = transport.send(RemoteCommand(cmd = "get_model_catalog", sessionId = sessionId)).catalog ?.takeUnless { it.version == 0L && it.models.isEmpty() } ModelCatalogLoadResult(catalog, null) } catch (cancelled: CancellationException) { @@ -755,11 +782,13 @@ public class RemoteSessionStore internal constructor( val failure: ModelCatalogFailure?, ) - private suspend fun sendListSessions(limit: Int, offset: Int, query: String): SessionListResponse = + private suspend fun sendListSessions(limit: Int, offset: Int, query: String, identity: RemoteWorkspaceIdentity): SessionListResponse = transport.send( RemoteCommand( cmd = "list_sessions", - workspacePath = workspacePath, + workspacePath = identity.path, + remoteConnectionId = identity.remoteConnectionId, + remoteSshHost = identity.remoteSshHost, limit = limit, offset = offset.toLong(), query = query.takeIf(String::isNotEmpty), @@ -853,6 +882,7 @@ public class RemoteSessionStore internal constructor( revision = current?.revision ?: authorityRevision, ) markConnected() + refreshModelCatalog() } catch (cancelled: CancellationException) { throw cancelled } catch (error: Throwable) { @@ -867,11 +897,14 @@ public class RemoteSessionStore internal constructor( private fun publishDurableTimeline() { val current = _state.value as? RemoteSessionUiState.Ready ?: return - _state.value = current.copy(timeline = timelineStore.snapshot(), hasMoreMessages = sessionHistoryHasMore) + val snapshot = timelineStore.snapshot() + if (current.selectedSessionId != snapshot.sessionId) return + _state.value = current.copy(timeline = snapshot, hasMoreMessages = sessionHistoryHasMore) markConnected() } - private fun subscribeSessionUpdates(sessionId: String) { + private fun subscribeSessionUpdates(sessionId: String): CompletableDeferred { + val initialHistory = CompletableDeferred() permissionMailbox.select(sessionId) sessionUpdates?.cancel() sessionHistoryHasMore = false @@ -883,7 +916,12 @@ public class RemoteSessionStore internal constructor( var caughtUp = false source.subscribe(sessionId, PersistentSessionReplica(store, source.streamIdentity + ":session:" + sessionId), { handleFailure(it, _state.value as? RemoteSessionUiState.Ready) }, - { caughtUp = true; publishDurableTimeline(); persistTranscript(sessionId, preserveOlder = sessionHistoryHasMore) }, + { + caughtUp = true + publishDurableTimeline() + persistTranscript(sessionId, preserveOlder = sessionHistoryHasMore) + initialHistory.complete(Unit) + }, ).collect { event -> check(event["session_id"]?.jsonPrimitive?.content == sessionId) { "Session binding mismatch" } val payload = event["payload"] as? JsonObject ?: error("Missing session event payload") @@ -920,13 +958,19 @@ public class RemoteSessionStore internal constructor( "failed", "error" -> ChatSyncPhase.ERROR else -> ChatSyncPhase.IDLE }) - publishDurableTimeline() + if (caughtUp) publishDurableTimeline() } } } } catch (cancelled: CancellationException) { throw cancelled } - catch (error: Throwable) { handleFailure(error, _state.value as? RemoteSessionUiState.Ready) } + catch (error: Throwable) { + initialHistory.completeExceptionally(error) + handleFailure(error, _state.value as? RemoteSessionUiState.Ready) + } finally { + initialHistory.cancel() + } } + return initialHistory } /** Hydrate history once, then receive durable invalidations through the shared transport. */ @@ -936,8 +980,11 @@ public class RemoteSessionStore internal constructor( resumableCursor: ChatSessionCursor? = null, ): OpenedSession? { if (timelineStore.snapshot().sessionId != sessionId) timelineStore.reset(sessionId) - subscribeSessionUpdates(sessionId) + val initialHistory = subscribeSessionUpdates(sessionId) val permission = readPermissionMode() + // A successful permission RPC does not mean the durable transcript has + // arrived. Keep cached content/loading until the initial replay is atomic. + initialHistory.await() if (!isCurrentWork(operationToken)) return null return OpenedSession(permission, sessionHistoryHasMore) } @@ -950,23 +997,37 @@ public class RemoteSessionStore internal constructor( private fun loadOlderMessages() { val current = _state.value as? RemoteSessionUiState.Ready ?: return val sessionId = current.selectedSessionId.orEmpty() - val beforeMessageId = current.timeline?.persistedMessages?.firstOrNull()?.id.orEmpty() - if (sessionId.isEmpty() || beforeMessageId.isEmpty() || !current.hasMoreMessages || current.busy) return - setBusy(current, true) - val operationToken = beginWork() - work = scope.launch { + // Pagination belongs to the durable cursor, including pages containing + // only deletions or control records with no visible chat message. + if (sessionId.isEmpty() || !current.hasMoreMessages || current.busy || historyWork?.isActive == true) return + // Pagination is an independent read, not a new session operation. It must + // not cancel model hydration or disable the composer while reading history. + val operationToken = workGeneration + val historyToken = ++historyGeneration + _state.value = current.copy(historyLoadState = HistoryLoadState.LOADING) + historyWork = scope.launch { try { val source = transport as? RemoteSessionStreamTransport ?: error("Durable session transport unavailable") source.loadOlder(sessionId) if (!isCurrentWork(operationToken) || timelineStore.snapshot().sessionId != sessionId) return@launch val ready = (_state.value as? RemoteSessionUiState.Ready) ?: current - _state.value = ready.copy(timeline = timelineStore.snapshot(), busy = false) + _state.value = ready.copy(timeline = timelineStore.snapshot()) } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { if (isCurrentWork(operationToken)) { - setBusy((_state.value as? RemoteSessionUiState.Ready) ?: current, false) + (_state.value as? RemoteSessionUiState.Ready)?.let { + _state.value = it.copy(historyLoadState = HistoryLoadState.FAILED) + } + } + } finally { + if (historyToken == historyGeneration) { + (_state.value as? RemoteSessionUiState.Ready)?.let { + if (it.historyLoadState == HistoryLoadState.LOADING) { + _state.value = it.copy(historyLoadState = HistoryLoadState.IDLE) + } + } } } } @@ -1032,13 +1093,18 @@ public class RemoteSessionStore internal constructor( return@launch } val targetWorkspacePath = requestedWorkspacePath.ifEmpty { if (assistantCreate) "" else workspacePath }.takeIf { it.isNotEmpty() } + val targetConnectionId = if (assistantCreate && requestedWorkspacePath.isEmpty()) null else + (if (requestedWorkspacePath.isNotEmpty()) intent.remoteConnectionId else workspaceConnectionId).orEmpty() + val targetSshHost = if (assistantCreate && requestedWorkspacePath.isEmpty()) null else + if (requestedWorkspacePath.isNotEmpty()) intent.remoteSshHost else workspaceSshHost val created = transport.send( RemoteCommand( cmd = "create_session", agentType = intent.agentType, sessionName = SessionNaming.wireSessionName(intent.agentType, intent.title), workspacePath = targetWorkspacePath, - remoteConnectionId = if (assistantCreate && requestedWorkspacePath.isEmpty()) null else (if (requestedWorkspacePath.isNotEmpty()) intent.remoteConnectionId else workspaceConnectionId).orEmpty(), + remoteConnectionId = targetConnectionId, + remoteSshHost = targetSshHost, ), ) val sessionId = created.resolvedSessionId?.trim().orEmpty() @@ -1051,6 +1117,12 @@ public class RemoteSessionStore internal constructor( // before optional initialization so cancellation or failure below // cannot turn an already-created remote session into a failed create. val now = Clock.System.now().toString() + val confirmedPath = created.workspacePath ?: targetWorkspacePath + val confirmedIdentity = confirmedPath?.takeIf(String::isNotBlank)?.let { path -> + RemoteWorkspaceIdentity(path, + if (created.workspacePath != null) created.remoteConnectionId else targetConnectionId, + if (created.workspacePath != null) created.remoteSshHost else targetSshHost) + } val confirmedSession = RemoteSession( id = sessionId, title = created.title?.takeIf(String::isNotBlank) @@ -1060,8 +1132,9 @@ public class RemoteSessionStore internal constructor( updatedAt = now, createdAt = now, messageCount = 0, - workspacePath = created.workspacePath?.takeIf(String::isNotBlank) ?: targetWorkspacePath, + workspacePath = confirmedPath, workspaceName = null, + workspaceIdentity = confirmedIdentity, ) if (!isCurrentWork(operationToken)) return@launch val commitRevision = publishCommittedCreate(confirmedSession, current) @@ -1107,6 +1180,7 @@ public class RemoteSessionStore internal constructor( draft = "", )) markConnected() + refreshModelCatalog() } catch (cancelled: CancellationException) { if (isCurrentWork(operationToken)) { if (isCommittedCreate(requestId)) { @@ -1473,14 +1547,23 @@ public class RemoteSessionStore internal constructor( val operationToken = beginWork() work = scope.launch { try { - transport.send( + val response = transport.send( RemoteCommand(cmd = "set_permission_mode", mode = wireMode), ) + check(!response.isError) { response.message ?: "Permission mode update failed" } + // Older hosts acknowledge the mutation without returning the mode. + // Read their authority instead of reporting the requested value as fact. + val confirmed = response.mode ?: transport.send( + RemoteCommand(cmd = "get_permission_mode"), + ).let { snapshot -> + check(!snapshot.isError) { snapshot.message ?: "Permission mode read failed" } + snapshot.mode + } if (!isCurrentWork(operationToken)) return@launch val ready = (_state.value as? RemoteSessionUiState.Ready) ?: current _state.value = ready.copy( busy = false, - permissionMode = intent.mode, + permissionMode = confirmed?.toUiMode() ?: SessionPermissionMode.UNKNOWN, permissionModeFailure = null, ) } catch (cancelled: CancellationException) { @@ -1501,14 +1584,13 @@ public class RemoteSessionStore internal constructor( private fun refreshPermissionMode() { val current = _state.value as? RemoteSessionUiState.Ready ?: return - setBusy(current, true) - val operationToken = beginWork() - work = scope.launch { + if (current.busy || permissionRefresh?.isActive == true) return + val operationToken = workGeneration + permissionRefresh = scope.launch { val permission = readPermissionMode() if (!isCurrentWork(operationToken)) return@launch val ready = (_state.value as? RemoteSessionUiState.Ready) ?: current _state.value = ready.copy( - busy = false, permissionMode = permission.mode ?: ready.permissionMode, permissionModeFailure = permission.failure, ) @@ -1522,41 +1604,53 @@ public class RemoteSessionStore internal constructor( * [RemoteSessionUiState.Ready] with the typed failure instead of taking the * transcript down with it. */ - private fun refreshModelCatalog() { + private fun refreshModelCatalog(invalidated: Boolean = false) { val current = _state.value as? RemoteSessionUiState.Ready ?: return - if (current.busy) return + if (modelCatalogRefresh?.isActive == true) { + if (invalidated) modelCatalogDirty = true + return + } + if (current.busy && !invalidated) return // Forward-compat: a future transport may produce a real unsupported-by- // peer signal. That is the only case where a retry cannot help, because // every generic failure is typed as LOAD_FAILED and remains retryable. if (modelCatalogFailure == ModelCatalogFailure.UNSUPPORTED_BY_PEER) return - setBusy(current, true) - val operationToken = beginWork() - work = scope.launch { + // A read of optional settings must not own the conversation's mutation + // slot. Superseding navigation/mutations still cancel and fence this read. + val operationToken = workGeneration + modelCatalogRefresh = scope.launch { try { - val result = loadModelCatalog(force = true) - if (!isCurrentWork(operationToken)) return@launch - commitModelCatalog(result) - val timeline = result.catalog?.let { catalog -> - val snapshot = timelineStore.snapshot() - if (snapshot.sessionId.isNotEmpty()) { - timelineStore.setModelCatalog(catalog, snapshot.selectedModelId) - timelineStore.snapshot() - } else { - null + do { + modelCatalogDirty = false + val result = loadModelCatalog(force = true, sessionId = current.selectedSessionId) + if (!isCurrentWork(operationToken)) return@launch + if (modelCatalogDirty) continue + commitModelCatalog(result) + val timeline = result.catalog?.let { catalog -> + val snapshot = timelineStore.snapshot() + if (snapshot.sessionId.isNotEmpty()) { + timelineStore.setModelCatalog( + catalog, + catalog.sessionModelId?.takeIf(String::isNotEmpty) ?: snapshot.selectedModelId, + ) + timelineStore.snapshot() + } else { + null + } } - } - val ready = (_state.value as? RemoteSessionUiState.Ready) ?: current - _state.value = ready.copy( - busy = false, - modelCatalog = result.catalog ?: ready.modelCatalog, - modelCatalogFailure = result.failure, - timeline = timeline ?: ready.timeline, - ) + val ready = (_state.value as? RemoteSessionUiState.Ready) ?: current + _state.value = ready.copy( + modelCatalog = result.catalog ?: ready.modelCatalog, + modelCatalogFailure = result.failure, + timeline = timeline ?: ready.timeline, + ) + } while (modelCatalogDirty) } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { if (isCurrentWork(operationToken)) { - setBusy((_state.value as? RemoteSessionUiState.Ready) ?: current, false) + val ready = _state.value as? RemoteSessionUiState.Ready ?: return@launch + _state.value = ready.copy(modelCatalogFailure = ModelCatalogFailure.LOAD_FAILED) } } } @@ -1589,12 +1683,17 @@ public class RemoteSessionStore internal constructor( } private fun runAction(sessionId: String, command: RemoteCommand) { + val toolId = command.toolId + val answers = command.answers as? JsonObject + if (command.cmd == "answer_question" && toolId != null && answers != null && + permissionMailbox.answer(sessionId, toolId, answers)) return val current = _state.value as? RemoteSessionUiState.Ready ?: return setBusy(current, true) val operationToken = beginWork() work = scope.launch { try { - transport.send(command.copy(sessionId = command.sessionId ?: sessionId)) + val result = transport.send(command.copy(sessionId = command.sessionId ?: sessionId)) + check(!result.isError) { result.message ?: "Remote action failed" } if (!isCurrentWork(operationToken)) return@launch setBusy((_state.value as? RemoteSessionUiState.Ready) ?: current, false) (transport as? RemoteSessionStreamTransport)?.wakeSessionStreams() @@ -1670,6 +1769,7 @@ public class RemoteSessionStore internal constructor( id = s.sessionId, title = s.title, agentType = s.agentType, status = s.status, updatedAt = s.updatedAt, createdAt = s.createdAt, messageCount = s.messageCount, workspacePath = s.workspacePath, workspaceName = s.workspaceName, + workspaceIdentity = s.workspaceIdentity?.let { RemoteWorkspaceIdentity(it.path, it.remoteConnectionId, it.remoteSshHost) }, ) private fun toPersistedSession(s: RemoteSession): PersistedRemoteSession = PersistedRemoteSession( @@ -1677,6 +1777,7 @@ public class RemoteSessionStore internal constructor( updatedAt = s.updatedAt, createdAt = s.createdAt, messageCount = s.messageCount, lastMessageId = "", workspacePath = s.workspacePath, workspaceName = s.workspaceName, pendingConfirmed = s.id in locallyCreatedSessions, + workspaceIdentity = s.workspaceIdentity?.let { PersistedWorkspaceIdentity(it.path, it.remoteConnectionId, it.remoteSshHost) }, ) @@ -1712,7 +1813,8 @@ public class RemoteSessionStore internal constructor( _connectionPhase.value = ConnectionPhase.FAILED } - /** Open transcripts recover through their durable stream; idle lists need their own health check. */ + /** Relay replay proves log availability, not that the controlled host is online. + * Keep foreground host probes for idle open conversations as well as lists. */ private fun setForeground(active: Boolean) { if (active && healthWork?.isActive == true) return val generation = ++healthGeneration @@ -1722,9 +1824,13 @@ public class RemoteSessionStore internal constructor( healthWork = scope.launch { while (generation == healthGeneration) { val ready = _state.value as? RemoteSessionUiState.Ready - if (ready != null && !ready.busy && ready.timeline == null) { + if (ready != null && !ready.busy) { try { - transport.send(RemoteCommand(cmd = "ping")) + kotlinx.coroutines.withTimeoutOrNull(10_000) { + transport.send( + RemoteCommand(cmd = "ping"), timeoutMs = 10_000, + ) + } ?: throw RelayTransportException(RelayFailure.Timeout) // A probe cannot overwrite a newer mutation's connection outcome. if (generation == healthGeneration && _state.value === ready) markConnected() } catch (cancelled: CancellationException) { diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionUiState.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionUiState.kt index fb4ba2e768..02623b59d0 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionUiState.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionUiState.kt @@ -2,6 +2,7 @@ package com.openbitfun.mobile.core.feature.session import com.openbitfun.mobile.core.domain.ChatTimelineState import com.openbitfun.mobile.core.domain.RemoteSession +import com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity import com.openbitfun.mobile.core.domain.SessionAgentTypes import com.openbitfun.mobile.core.protocol.RemoteModelCatalog @@ -100,6 +101,9 @@ public enum class CreateSessionOperationFailure { CANCELLED, } +/** Independent backward-page request state; never blocks live generation. */ +public enum class HistoryLoadState { IDLE, LOADING, FAILED } + /** Why the optional model catalog could not be loaded. */ public enum class ModelCatalogFailure { /** @@ -161,22 +165,27 @@ public data class WorkspaceSessionDirectoryEntry public constructor( public val path: String, public val status: WorkspaceSessionDirectoryStatus, public val sessions: List, -) + public val remoteConnectionId: String?, + public val remoteSshHost: String?, +) { + public constructor(path: String, status: WorkspaceSessionDirectoryStatus, sessions: List) : + this(path, status, sessions, null, null) + + public val identity: RemoteWorkspaceIdentity get() = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) +} public data class WorkspaceSessionDirectoryUiState public constructor( public val workspaces: List, ) { - public fun workspace(path: String): WorkspaceSessionDirectoryEntry? { - val normalized = normalizeWorkspaceSessionPath(path) - return workspaces.firstOrNull { normalizeWorkspaceSessionPath(it.path) == normalized } + public fun workspace(path: String): WorkspaceSessionDirectoryEntry? = workspace(path, null, null) + + public fun workspace(path: String, remoteConnectionId: String?, remoteSshHost: String?): WorkspaceSessionDirectoryEntry? { + val identity = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost) + return workspaces.firstOrNull { it.identity.matches(identity) } } } -private fun normalizeWorkspaceSessionPath(path: String): String { - val trimmed = path.trim() - val normalized = trimmed.trimEnd('/') - return normalized.ifEmpty { trimmed } -} + public data class ComposerImage public constructor( public val id: String, @@ -232,7 +241,17 @@ public sealed interface RemoteSessionUiState { public val revision: Long, public val lastSentMessage: SentChatMessage?, public val permissionMailbox: PermissionMailboxUiState, + public val historyLoadState: HistoryLoadState, ) : RemoteSessionUiState { + public constructor( + sessions: List, selectedSessionId: String?, timeline: ChatTimelineState?, busy: Boolean, + permissionMode: SessionPermissionMode?, permissionModeFailure: PermissionModeFailure?, query: String, + agentFilter: SessionAgentFilter, hasMore: Boolean, hasMoreMessages: Boolean, + modelCatalog: RemoteModelCatalog?, modelCatalogFailure: ModelCatalogFailure?, draft: String, + revision: Long, lastSentMessage: SentChatMessage?, permissionMailbox: PermissionMailboxUiState, + ) : this(sessions, selectedSessionId, timeline, busy, permissionMode, permissionModeFailure, query, + agentFilter, hasMore, hasMoreMessages, modelCatalog, modelCatalogFailure, draft, revision, + lastSentMessage, permissionMailbox, HistoryLoadState.IDLE) public constructor( sessions: List, selectedSessionId: String?, timeline: ChatTimelineState?, busy: Boolean, permissionMode: SessionPermissionMode?, permissionModeFailure: PermissionModeFailure?, query: String, @@ -329,7 +348,7 @@ public sealed interface RemoteSessionIntent { public data class RespondPermission(public val requestId: String, public val approve: Boolean, public val updatedInput: String?) : RemoteSessionIntent public data object RefreshPermissionMailbox : RemoteSessionIntent - /** Native lifecycle controls the idle connection health probe. */ + /** Native lifecycle controls foreground connection health probes. */ public data class SetForeground(public val active: Boolean) : RemoteSessionIntent public data object Load : RemoteSessionIntent @@ -345,11 +364,19 @@ public sealed interface RemoteSessionIntent { /** Load one sidebar workspace branch without changing the desktop's active workspace. */ public data class LoadWorkspaceSessions public constructor( public val path: String, - ) : RemoteSessionIntent + public val remoteConnectionId: String?, + public val remoteSshHost: String?, + ) : RemoteSessionIntent { + public constructor(path: String) : this(path, null, null) + } public data class RetryWorkspaceSessions public constructor( public val path: String, - ) : RemoteSessionIntent + public val remoteConnectionId: String?, + public val remoteSshHost: String?, + ) : RemoteSessionIntent { + public constructor(path: String) : this(path, null, null) + } public data class Search public constructor( public val query: String, @@ -377,7 +404,9 @@ public sealed interface RemoteSessionIntent { public val modelId: String?, public val workspacePath: String?, public val remoteConnectionId: String?, + public val remoteSshHost: String?, ) : RemoteSessionIntent { + public constructor(agentType: String, title: String, instruction: String, modelId: String?, workspacePath: String?, remoteConnectionId: String?) : this(agentType, title, instruction, modelId, workspacePath, remoteConnectionId, null) public constructor(agentType: String, title: String, instruction: String, modelId: String?, workspacePath: String?) : this(agentType, title, instruction, modelId, workspacePath, null) public constructor( agentType: String, @@ -398,7 +427,9 @@ public sealed interface RemoteSessionIntent { public val modelId: String?, public val workspacePath: String?, public val remoteConnectionId: String?, + public val remoteSshHost: String?, ) : RemoteSessionIntent { + public constructor(requestId: String, agentType: String, title: String, instruction: String, modelId: String?, workspacePath: String?, remoteConnectionId: String?) : this(requestId, agentType, title, instruction, modelId, workspacePath, remoteConnectionId, null) public constructor(requestId: String, agentType: String, title: String, instruction: String, modelId: String?, workspacePath: String?) : this(requestId, agentType, title, instruction, modelId, workspacePath, null) public constructor( requestId: String, @@ -496,9 +527,9 @@ public sealed interface RemoteSessionIntent { * the open transcript, as [RefreshPermissionMode] does for the permission * mode. * - * No session id, for the same reason [RefreshPermissionMode] carries none: - * `get_model_catalog` is addressed to the desktop and one catalog answers - * for every session on it. + * The store supplies the currently selected session id so the host also + * returns that session's authoritative model selection. Callers do not + * carry an id that could become stale after navigation. */ public data object RefreshModelCatalog : RemoteSessionIntent diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplica.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplica.kt index d53119af88..92ff7a42b8 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplica.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplica.kt @@ -34,8 +34,8 @@ internal class SessionRecordReplica(private val sessionId: String) { val recordId = payload.string("id") check(recordId.substringBefore('/') in setOf("turn", "round", "item") && recordId.substringAfter('/', "").isNotEmpty()) { "Invalid session record identity" } if (revision <= (recordVersions[recordId] ?: -1)) return - recordVersions[recordId] = revision if (payload["deleted"]?.jsonPrimitive?.booleanOrNull == true) { + recordVersions[recordId] = revision if (revision > (tombstones[recordId] ?: -1)) tombstones[recordId] = revision return } @@ -49,22 +49,29 @@ internal class SessionRecordReplica(private val sessionId: String) { check(recordItem == null || recordRound != null) { "Item has no round" } val expectedId = if (recordItem != null) "item/" + recordItem.getValue("data").jsonObject.string("id") else if (recordRound != null) "round/" + recordRound.string("id") else "turn/$turnId" check(recordId == expectedId) { "Session record identity mismatch" } + val roundId = recordRound?.string("id") + if (recordRound != null) { + check(recordRound.string("turnId") == turnId) { "Round binding mismatch" } + } + val itemId = recordItem?.getValue("data")?.jsonObject?.string("id") + if (itemId != null) { + check(itemRounds[itemId] == null || itemRounds[itemId] == roundId) { "Item parent changed" } + } + + // Validate the complete ancestry before changing either data or replay fences. + // A rejected record must leave a corrected delivery at the same revision usable. + recordVersions[recordId] = revision put(turns, turnId, revision, turn) if (turns[turnId]?.value?.string("status") != "inprogress") controls.entries.removeAll { it.value.first == turnId } - val round = payload["round"] as? JsonObject - if (round != null) { - check(round.string("turnId") == turnId) { "Round binding mismatch" } - val roundId = round.string("id") - put(rounds, roundId, revision, round) - val item = payload["item"] as? JsonObject - if (item != null) { - val id = item.getValue("data").jsonObject.string("id") - check(itemRounds[id] == null || itemRounds[id] == roundId) { "Item parent changed" } - itemRounds[id] = roundId - put(items, id, revision, item) + if (recordRound != null && roundId != null) { + put(rounds, roundId, revision, recordRound) + if (recordItem != null && itemId != null) { + itemRounds[itemId] = roundId + put(items, itemId, revision, recordItem) } } } + fun messages(): List = turns.filter { (id, record) -> record.revision > (tombstones["turn/$id"] ?: -1) }.values.sortedBy { it.value.number("turnIndex") }.flatMap { record -> val turn = record.value val turnId = turn.string("turnId") @@ -73,18 +80,23 @@ internal class SessionRecordReplica(private val sessionId: String) { val children = rounds.filter { (id, record) -> record.revision > maxOf(turnFence, tombstones["round/$id"] ?: -1) }.values.map { it.value }.filter { it.string("turnId") == turnId } .sortedBy { it.number("roundIndex") }.flatMap { round -> items.filter { (id, record) -> itemRounds[id] == round.string("id") && record.revision > maxOf(turnFence, tombstones["round/" + round.string("id")] ?: -1, tombstones["item/$id"] ?: -1) }.values.map { it.value } - .filter { it.getValue("data").jsonObject.string("status") != "superseded" } + .filter { it.getValue("data").jsonObject.string("status") !in setOf("superseded", "retry_superseded") } .sortedWith(compareBy({ it.getValue("data").jsonObject.number("orderIndex") }, { it.getValue("data").jsonObject.number("timestamp") })) } val rendered = children.map { item -> val data = item.getValue("data").jsonObject val type = item.string("type") val result = data["toolResult"] as? JsonObject - ChatMessageItemResponse(type = type, content = data.string("content"), isSubagent = data["isSubagentItem"]?.jsonPrimitive?.booleanOrNull, + ChatMessageItemResponse(type = type, content = data.string("content"), isSubagent = data["isSubagentItem"]?.jsonPrimitive?.booleanOrNull == true || data.string("subagentSessionId").isNotEmpty(), tool = if (type != "tool") null else RemoteToolStatusResponse( - id = (data["toolCall"] as? JsonObject)?.string("id")?.takeIf { it.isNotEmpty() } ?: data.string("id"), name = data.string("toolName"), status = data.string("status"), + id = (data["toolCall"] as? JsonObject)?.string("id")?.takeIf { it.isNotEmpty() } ?: data.string("id"), name = data.string("toolName"), + status = data.string("status").takeIf { it.isNotEmpty() } + ?: if (result == null) "running" else if (result["success"]?.jsonPrimitive?.booleanOrNull == true) "completed" else "failed", toolInput = (data["toolCall"] as? JsonObject)?.get("input"), toolOutput = result?.get("result"), - errorPreview = result?.string("error"), durationMs = data["durationMs"]?.jsonPrimitive?.longOrNull)) + errorPreview = result?.string("error"), + startMs = data["startTime"]?.jsonPrimitive?.longOrNull, + durationMs = data["durationMs"]?.jsonPrimitive?.longOrNull + ?: result?.get("durationMs")?.jsonPrimitive?.longOrNull)) } rendered.mapNotNull { it.tool }.filter { it.status in setOf("completed", "failed", "cancelled", "rejected", "skipped") }.forEach { controls.remove(it.id) } val controlTools = controls.values.filter { it.first == turnId }.map { it.second } @@ -92,8 +104,8 @@ internal class SessionRecordReplica(private val sessionId: String) { controlTools.filter { tool -> rendered.none { it.tool?.id == tool.id } }.map { ChatMessageItemResponse(type = "tool", tool = it) } listOf(RemoteResponseMapper.chatMessage(ChatMessageResponse(id = user.string("id"), role = "user", content = user.string("content"), turnId = turnId, metadata = user["metadata"], timestamp = user.string("timestamp"))), RemoteResponseMapper.chatMessage(ChatMessageResponse(id = "${turnId}_assistant", role = "assistant", turnId = turnId, - content = rendered.filter { it.type == "text" }.joinToString("") { it.content.orEmpty() }, - thinking = rendered.filter { it.type == "thinking" }.joinToString("") { it.content.orEmpty() }, + content = rendered.filter { it.type == "text" && it.isSubagent != true }.joinToString("") { it.content.orEmpty() }, + thinking = rendered.filter { it.type == "thinking" && it.isSubagent != true }.joinToString("") { it.content.orEmpty() }, items = shownItems, status = when (turn.string("status")) { "inprogress" -> "streaming"; "error" -> "failed"; else -> turn.string("status") }, error = turn.string("error"), metadata = turn))) } private fun JsonObject.string(key: String): String = (get(key) as? JsonPrimitive)?.contentOrNull.orEmpty() diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentation.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentation.kt index 8354417600..8df074993b 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentation.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentation.kt @@ -1,7 +1,12 @@ package com.openbitfun.mobile.core.feature.shell +import com.openbitfun.mobile.core.domain.RemoteSession +import com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity +import com.openbitfun.mobile.core.domain.identity +import com.openbitfun.mobile.core.domain.belongsTo import com.openbitfun.mobile.core.feature.session.RemoteSessionUiState import com.openbitfun.mobile.core.feature.workspace.RemoteWorkspaceUiState +import com.openbitfun.mobile.core.feature.workspace.projectWorkspaceCatalog /** One remote session with only the facts the unified sidebar renders. */ public data class RemoteSidebarSessionRow public constructor( @@ -17,7 +22,10 @@ public data class RemoteSidebarWorkspaceRow public constructor( public val selected: Boolean, public val sessions: List, public val remoteConnectionId: String?, + public val remoteSshHost: String?, ) { + public val key: String get() = RemoteWorkspaceIdentity(path, remoteConnectionId, remoteSshHost).key + public constructor(path: String, name: String, selected: Boolean, sessions: List, remoteConnectionId: String?) : this(path, name, selected, sessions, remoteConnectionId, null) public constructor(path: String, name: String, selected: Boolean, sessions: List) : this(path, name, selected, sessions, null) } @@ -26,30 +34,29 @@ public object RemoteSidebarPresentation { public fun workspaces( workspaceState: RemoteWorkspaceUiState.Ready?, sessionState: RemoteSessionUiState.Ready?, + ): List = workspacesForSessions(workspaceState, sessionState?.sessions.orEmpty()) + + public fun workspacesForSessions( + workspaceState: RemoteWorkspaceUiState.Ready?, + sessions: List, ): List { if (workspaceState == null) return emptyList() val selected = workspaceState.selected - val workspaceRows = buildList { - if (selected != null && selected.path.isNotBlank()) { - add(Triple(selected.path, selected.name, selected.remoteConnectionId)) - } - workspaceState.assistants.forEach { assistant -> - if (assistant.path.isNotBlank() && none { it.first == assistant.path && it.third == null }) add(Triple(assistant.path, assistant.name, null)) - } - workspaceState.workspaces.forEach { workspace -> - if (workspace.path.isNotBlank() && none { it.first == workspace.path && it.third == workspace.remoteConnectionId }) { - add(Triple(workspace.path, workspace.name, workspace.remoteConnectionId)) - } - } - } - return workspaceRows.map { (path, name, connectionId) -> + val workspaceRows = (workspaceState.catalog ?: projectWorkspaceCatalog( + workspaceState.workspaces, workspaceState.assistants, + )).workspaces + return workspaceRows.map { workspace -> + val path = workspace.path + val connectionId = workspace.remoteConnectionId RemoteSidebarWorkspaceRow( path = path, - name = name, - selected = path == selected?.path && connectionId == selected.remoteConnectionId, + name = workspace.name, + selected = selected != null && workspace.identity().matches( + RemoteWorkspaceIdentity(selected.path, selected.remoteConnectionId, selected.remoteSshHost)), remoteConnectionId = connectionId, - sessions = sessionState?.sessions.orEmpty() - .filter { (it.workspacePath ?: selected?.path) == path } + remoteSshHost = workspace.remoteSshHost, + sessions = sessions + .filter { it.belongsTo(workspace.identity(), workspaceRows.map { row -> row.identity() }) } .map { session -> RemoteSidebarSessionRow( id = session.id, diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStore.kt index ba71cf19fa..bc42689bb1 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStore.kt @@ -85,7 +85,8 @@ public class RemoteWorkspaceStore internal constructor( } updateReady { it.copy(workspaces = recent.workspaces.map { item -> RecentWorkspace(item.path.orEmpty(), item.name ?: basename(item.path.orEmpty()), item.lastOpened, item.workspaceKind.orEmpty(), item.remoteSshHost, item.remoteConnectionId) - }, assistants = assistants.assistants.map { item -> WorkspaceAssistant(item.path, item.name, item.assistantId) }, loadFailure = false) } + }, assistants = assistants.assistants.map { item -> WorkspaceAssistant(item.path, item.name, item.assistantId) }, + catalog = recent.sidebarCatalog(assistants.assistants.map { item -> WorkspaceAssistant(item.path, item.name, item.assistantId) }), loadFailure = false) } } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { updateReady { it.copy(loadFailure = true) } } } @@ -101,6 +102,14 @@ public class RemoteWorkspaceStore internal constructor( private var work: Job? = null private var previewWork: Job? = null private var downloadWork: Job? = null + private data class DownloadBinding( + val target: FilePreviewTarget, + val workspacePath: String?, + val connectionId: String?, + val generation: Long, + val stopVersion: Long, + ) + private var downloadBinding: DownloadBinding? = null private var loadGeneration: Long = 0 private var targetEpoch: Int = 0 private var previewGeneration: Long = 0 @@ -156,6 +165,16 @@ public class RemoteWorkspaceStore internal constructor( } is RemoteWorkspaceIntent.SortFiles -> files.sort(intent.sort) RemoteWorkspaceIntent.CloseFileEditor -> files.closeFile() + is RemoteWorkspaceIntent.OpenDeviceTools -> openDeviceTools(intent.path, intent.connectionId) + is RemoteWorkspaceIntent.SelectDeviceToolsPanel -> updateReady { it.copy(deviceTools = it.deviceTools.copy(panel = intent.panel)) } + RemoteWorkspaceIntent.CloseDeviceTools -> { + deviceToolGeneration++; deviceToolAction?.cancel() + updateReady { it.copy(deviceTools = it.deviceTools.copy(visible = false, busy = false)) } + } + RemoteWorkspaceIntent.StartDeviceToolsTerminal -> { + val tools = (_state.value as? RemoteWorkspaceUiState.Ready)?.deviceTools + if (tools != null && tools.visible && !tools.busy && !tools.failed && tools.path.isNotEmpty()) terminal.open(tools.path, tools.connectionId) + } is RemoteWorkspaceIntent.OpenDeviceFiles -> openDeviceTool(intent.path, intent.remoteConnectionId, false) is RemoteWorkspaceIntent.OpenDeviceTerminal -> openDeviceTool(intent.path, intent.remoteConnectionId, true) is RemoteWorkspaceIntent.BrowseFiles -> { @@ -174,6 +193,10 @@ public class RemoteWorkspaceStore internal constructor( is RemoteWorkspaceIntent.RenameFile -> files.renameFile(intent.path) RemoteWorkspaceIntent.DeleteFile -> files.deleteFile() is RemoteWorkspaceIntent.CreateDirectory -> files.createDirectory(intent.path) + is RemoteWorkspaceIntent.UploadFileEntry -> files.uploadEntry(intent.name, intent.source) + is RemoteWorkspaceIntent.CreateFileEntry -> files.createEntry(intent.name, intent.directory) + is RemoteWorkspaceIntent.RenameFileEntry -> files.renameEntry(intent.path, intent.name) + is RemoteWorkspaceIntent.DeleteFileEntry -> files.deleteEntry(intent.path) RemoteWorkspaceIntent.OpenTerminal -> { if (terminalObserver == null) terminalObserver = scope.launch { terminal.state.collect { value -> updateReady { it.copy(terminal = value) } } } val selected = (_state.value as? RemoteWorkspaceUiState.Ready)?.selected @@ -182,11 +205,13 @@ public class RemoteWorkspaceStore internal constructor( } is RemoteWorkspaceIntent.ResizeTerminal -> terminal.resize(intent.cols, intent.rows) RemoteWorkspaceIntent.CloseTerminal -> terminal.close() + RemoteWorkspaceIntent.ReopenTerminal -> terminal.reopen() is RemoteWorkspaceIntent.WriteTerminal -> terminal.write(intent.data) is RemoteWorkspaceIntent.SelectWorkspace -> selectWorkspace(intent) is RemoteWorkspaceIntent.SelectAssistant -> selectAssistant(intent.path) is RemoteWorkspaceIntent.OpenFile -> resolveAndOpenFile(intent) is RemoteWorkspaceIntent.DownloadFile -> resolveAndDownloadFile(intent) + RemoteWorkspaceIntent.RetryDownload -> retryDownload() is RemoteWorkspaceIntent.DownloadSaved -> finishDownload(intent.reference, true) is RemoteWorkspaceIntent.DownloadSaveFailed -> finishDownload(intent.reference, false) RemoteWorkspaceIntent.DismissPreview -> { @@ -198,6 +223,8 @@ public class RemoteWorkspaceStore internal constructor( } public fun stop() { + updateReady { it.copy(deviceTools = DeviceToolsUiState()) } + downloadBinding = null deviceToolGeneration++; deviceToolAction?.cancel() directoryPicker.reset(); directoryPickerObserver?.cancel(); directoryPickerObserver = null; fileWorkspace = null catalogSubscription?.cancel(); catalogRefresh?.cancel() @@ -228,7 +255,7 @@ public class RemoteWorkspaceStore internal constructor( } /** Device-directory catalog request; it must not depend on the desktop's active workspace. */ - internal suspend fun directoryCatalog(): List = coroutineScope { + internal suspend fun directoryCatalog(): WorkspaceCatalogUiState = coroutineScope { val recentDeferred = async { transport.send(RemoteCommand(cmd = "list_recent_workspaces")) } @@ -257,7 +284,7 @@ public class RemoteWorkspaceStore internal constructor( // The remote catalog remains authoritative when its optional cache is unavailable. } } - mergedCatalog(loadedWorkspaces, loadedAssistants) + recent.sidebarCatalog(loadedAssistants) } private fun load() { @@ -329,7 +356,7 @@ public class RemoteWorkspaceStore internal constructor( busy = false, download = RemoteFileDownloadUiState.None, loadFailure = false, - ) + ).copy(catalog = recent.sidebarCatalog(loadedAssistants)) if (catalogDirty && catalogRefresh?.isActive != true) refreshCatalog() try { val connections = transport.send(RemoteCommand( @@ -369,7 +396,9 @@ public class RemoteWorkspaceStore internal constructor( private fun selectWorkspace(intent: RemoteWorkspaceIntent.SelectWorkspace) { val normalized = intent.path.trim() if (normalized.isEmpty()) return - val candidates = (_state.value as? RemoteWorkspaceUiState.Ready)?.workspaces.orEmpty().filter { it.path == normalized } + val candidates = if (intent.inferSavedIdentity) { + (_state.value as? RemoteWorkspaceUiState.Ready)?.workspaces.orEmpty().filter { it.path == normalized } + } else emptyList() if (intent.remoteConnectionId == null && intent.remoteSshHost == null && candidates.size > 1) { failRetainingCache() return @@ -531,16 +560,39 @@ public class RemoteWorkspaceStore internal constructor( downloadFile(target) } - private fun openDeviceTool(path: String, connectionId: String?, terminalTool: Boolean) { + private fun bindTerminal(location: String, connectionId: String?) { + terminalObserver?.cancel() + terminal = workspaceTerminals.getOrPut(location to connectionId) { RuntimeTerminalStore(scope, transport, relayStreams) } + updateReady { it.copy(terminal = terminal.state.value) } + terminalObserver = scope.launch { terminal.state.collect { value -> updateReady { it.copy(terminal = value) } } } + } + + private fun openDeviceTools(path: String, connectionId: String?) { + val ready = _state.value as? RemoteWorkspaceUiState.Ready ?: return + // The editor must be closed through its discard/save flow before changing providers. + if (ready.deviceTools.visible && ready.files.file != null) return + val panel = if (ready.deviceTools.visible) ready.deviceTools.panel else DeviceToolsPanel.FILES + updateReady { it.copy(deviceTools = DeviceToolsUiState(true, panel, "", connectionId, true, false)) } + openDeviceTool(path, connectionId, false, unifiedTools = true) + } + + private fun openDeviceTool(path: String, connectionId: String?, terminalTool: Boolean, unifiedTools: Boolean = false) { val ready = _state.value as? RemoteWorkspaceUiState.Ready ?: return deviceToolAction?.cancel() val generation = ++deviceToolGeneration + // Detach the previous provider before validating the replacement: even + // a rejected selection must fence late directory responses from it. + if (!terminalTool) { + filesObserver?.cancel(); filesObserver = null + files.reset(); fileWorkspace = null + updateReady { it.copy(files = files.state.value.copy(busy = true)) } + } if (connectionId != null && ready.savedConnections.none { it.id == connectionId }) { + if (unifiedTools) updateReady { it.copy(deviceTools = it.deviceTools.copy(busy = false, failed = true)) } updateReady { if (terminalTool) it.copy(terminal = RuntimeTerminalUiState(null, "", false, true)) else it.copy(files = RuntimeFilesUiState("", emptyList(), false, null, "", false, true)) } return } if (terminalTool) updateReady { it.copy(terminal = RuntimeTerminalUiState(null, "", true, false)) } - else { filesObserver?.cancel(); filesObserver = null; files.reset(); fileWorkspace = null; updateReady { it.copy(files = it.files.copy(busy = true)) } } deviceToolAction = scope.launch { try { val location = path.takeIf { it.isNotBlank() } ?: if (connectionId != null) "/" else { @@ -550,12 +602,13 @@ public class RemoteWorkspaceStore internal constructor( } if (generation != deviceToolGeneration) return@launch if (terminalTool) { - terminalObserver?.cancel() - terminal = workspaceTerminals.getOrPut(location to connectionId) { RuntimeTerminalStore(scope, transport, relayStreams) } - updateReady { it.copy(terminal = terminal.state.value) } - terminalObserver = scope.launch { terminal.state.collect { value -> updateReady { it.copy(terminal = value) } } } + bindTerminal(location, connectionId) terminal.open(location, connectionId) } else { + if (unifiedTools) { + bindTerminal(location, connectionId) + updateReady { it.copy(deviceTools = it.deviceTools.copy(path = location, busy = false, failed = false)) } + } if (filesObserver == null) filesObserver = scope.launch { files.state.collect { value -> if (value.directory.isNotEmpty() && !value.failed) fileWorkspace = value.directory to connectionId updateReady { it.copy(files = value) } @@ -565,21 +618,38 @@ public class RemoteWorkspaceStore internal constructor( } } catch (cancelled: CancellationException) { throw cancelled } catch (_: Throwable) { + if (generation == deviceToolGeneration && unifiedTools) updateReady { it.copy(deviceTools = it.deviceTools.copy(busy = false, failed = true)) } if (generation == deviceToolGeneration) updateReady { if (terminalTool) it.copy(terminal = RuntimeTerminalUiState(null, "", false, true)) else it.copy(files = it.files.copy(busy = false, failed = true)) } } } } - private fun downloadFile(target: FilePreviewTarget) { + private fun retryDownload() { + val failed = (_state.value as? RemoteWorkspaceUiState.Ready)?.download as? RemoteFileDownloadUiState.Failed ?: return + val binding = downloadBinding ?: return + if (!failed.retryable || failed.target != binding.target || + binding.generation != loadGeneration || binding.stopVersion != _stopVersion.value) return + downloadFile(binding.target, binding) + } + + private fun downloadFile(target: FilePreviewTarget, previousBinding: DownloadBinding? = null) { val current = _state.value as? RemoteWorkspaceUiState.Ready ?: return if (current.busy || current.download is RemoteFileDownloadUiState.Loading || current.download is RemoteFileDownloadUiState.AwaitingSave ) return - if (target.sessionId.isEmpty() && fileWorkspace == null && current.selected?.kind == "remote" && current.selected.remoteConnectionId.isNullOrBlank()) { + if (previousBinding == null && target.sessionId.isEmpty() && fileWorkspace == null && current.selected?.kind == "remote" && current.selected.remoteConnectionId.isNullOrBlank()) { failDownload(target, "Remote workspace connection identity is unavailable"); return } - val workspacePath = (fileWorkspace?.first ?: current.selected?.path).takeIf { target.sessionId.isEmpty() } - val connectionId = (fileWorkspace?.let { it.second } ?: current.selected?.remoteConnectionId.takeIf { fileWorkspace == null }).takeIf { target.sessionId.isEmpty() } + val binding = previousBinding ?: DownloadBinding( + target, + (fileWorkspace?.first ?: current.selected?.path).takeIf { target.sessionId.isEmpty() }, + (fileWorkspace?.let { it.second } ?: current.selected?.remoteConnectionId.takeIf { fileWorkspace == null }).takeIf { target.sessionId.isEmpty() }, + loadGeneration, + _stopVersion.value, + ) + downloadBinding = binding + val workspacePath = binding.workspacePath + val connectionId = binding.connectionId downloadWork?.cancel() _state.value = current.copy(download = RemoteFileDownloadUiState.Loading(target, 0, 0)) val downloadGeneration = loadGeneration @@ -884,13 +954,7 @@ public class RemoteWorkspaceStore internal constructor( workspaces: List, assistants: List, ): List { - val merged = workspaces.toMutableList() - assistants.forEach { assistant -> - if (merged.none { it.path == assistant.path }) { - merged += RecentWorkspace(assistant.path, assistant.name, "", ASSISTANT_KIND) - } - } - return merged + return projectWorkspaceCatalog(workspaces, assistants).workspaces } public companion object { diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceUiState.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceUiState.kt index 07890f29ee..622557f340 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceUiState.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceUiState.kt @@ -148,6 +148,19 @@ public data class SavedRuntimeConnectionUiState public constructor( public val id: String, public val name: String, public val host: String, ) +/** Device tools are independent of chat/workspace selection. Labels stay native. */ +public enum class DeviceToolsPanel { FILES, TERMINAL } +public data class DeviceToolsUiState public constructor( + public val visible: Boolean, + public val panel: DeviceToolsPanel, + public val path: String, + public val connectionId: String?, + public val busy: Boolean, + public val failed: Boolean, +) { + public constructor() : this(false, DeviceToolsPanel.FILES, "", null, false, false) +} + public sealed interface RemoteWorkspaceUiState { public data object Idle : RemoteWorkspaceUiState public data object Loading : RemoteWorkspaceUiState @@ -167,7 +180,11 @@ public sealed interface RemoteWorkspaceUiState { public val terminal: RuntimeTerminalUiState, public val files: RuntimeFilesUiState, public val directoryPicker: RuntimeFilesUiState, + public val catalog: WorkspaceCatalogUiState?, + public val deviceTools: DeviceToolsUiState, ) : RemoteWorkspaceUiState { + public constructor(workspaces: List, assistants: List, selected: SelectedWorkspace?, preview: RemoteFilePreviewUiState, busy: Boolean, download: RemoteFileDownloadUiState, loadFailure: Boolean, hostCapabilities: List, savedConnections: List, savedConnectionsFailure: Boolean, terminal: RuntimeTerminalUiState, files: RuntimeFilesUiState, directoryPicker: RuntimeFilesUiState, catalog: WorkspaceCatalogUiState?) : this(workspaces, assistants, selected, preview, busy, download, loadFailure, hostCapabilities, savedConnections, savedConnectionsFailure, terminal, files, directoryPicker, catalog, DeviceToolsUiState()) + public constructor(workspaces: List, assistants: List, selected: SelectedWorkspace?, preview: RemoteFilePreviewUiState, busy: Boolean, download: RemoteFileDownloadUiState, loadFailure: Boolean, hostCapabilities: List, savedConnections: List, savedConnectionsFailure: Boolean, terminal: RuntimeTerminalUiState, files: RuntimeFilesUiState, directoryPicker: RuntimeFilesUiState) : this(workspaces, assistants, selected, preview, busy, download, loadFailure, hostCapabilities, savedConnections, savedConnectionsFailure, terminal, files, directoryPicker, null) public constructor(workspaces: List, assistants: List, selected: SelectedWorkspace?, preview: RemoteFilePreviewUiState, busy: Boolean, download: RemoteFileDownloadUiState, loadFailure: Boolean, hostCapabilities: List, savedConnections: List, savedConnectionsFailure: Boolean, terminal: RuntimeTerminalUiState, files: RuntimeFilesUiState) : this(workspaces, assistants, selected, preview, busy, download, loadFailure, hostCapabilities, savedConnections, savedConnectionsFailure, terminal, files, RuntimeFilesUiState("", emptyList(), false, null, "", false, false)) public constructor( workspaces: List, assistants: List, selected: SelectedWorkspace?, @@ -196,6 +213,13 @@ public sealed interface RemoteWorkspaceIntent { public data class ResizeTerminal(public val cols: Int, public val rows: Int) : RemoteWorkspaceIntent public data class UploadFile(public val path: String, public val source: RuntimeUploadSource) : RemoteWorkspaceIntent public data object Load : RemoteWorkspaceIntent + public data class OpenDeviceTools(public val path: String, public val connectionId: String?) : RemoteWorkspaceIntent { + public constructor() : this("", null) + public constructor(connectionId: String?) : this("", connectionId) + } + public data class SelectDeviceToolsPanel(public val panel: DeviceToolsPanel) : RemoteWorkspaceIntent + public data object CloseDeviceTools : RemoteWorkspaceIntent + public data object StartDeviceToolsTerminal : RemoteWorkspaceIntent public data class OpenDeviceFiles(public val path: String, public val remoteConnectionId: String?) : RemoteWorkspaceIntent public data class OpenDeviceTerminal(public val path: String, public val remoteConnectionId: String?) : RemoteWorkspaceIntent public data class BrowseFiles(public val path: String, public val append: Boolean) : RemoteWorkspaceIntent @@ -208,10 +232,17 @@ public sealed interface RemoteWorkspaceIntent { public data class RenameFile(public val path: String) : RemoteWorkspaceIntent public data object DeleteFile : RemoteWorkspaceIntent public data class CreateDirectory(public val path: String) : RemoteWorkspaceIntent + public data class UploadFileEntry(public val name: String, public val source: RuntimeUploadSource) : RemoteWorkspaceIntent + public data class CreateFileEntry(public val name: String, public val directory: Boolean) : RemoteWorkspaceIntent + public data class RenameFileEntry(public val path: String, public val name: String) : RemoteWorkspaceIntent + public data class DeleteFileEntry(public val path: String) : RemoteWorkspaceIntent public data object OpenTerminal : RemoteWorkspaceIntent + public data object ReopenTerminal : RemoteWorkspaceIntent public data object CloseTerminal : RemoteWorkspaceIntent public data class WriteTerminal(public val data: String) : RemoteWorkspaceIntent - public data class SelectWorkspace public constructor(public val path: String, public val remoteConnectionId: String?, public val remoteSshHost: String?) : RemoteWorkspaceIntent { + /** Explicit location pickers disable inference: null then means the controlled host itself. */ + public data class SelectWorkspace public constructor(public val path: String, public val remoteConnectionId: String?, public val remoteSshHost: String?, public val inferSavedIdentity: Boolean) : RemoteWorkspaceIntent { + public constructor(path: String, remoteConnectionId: String?, remoteSshHost: String?) : this(path, remoteConnectionId, remoteSshHost, true) public constructor(path: String) : this(path, null, null) } public data class SelectAssistant public constructor(public val path: String) : RemoteWorkspaceIntent @@ -230,6 +261,7 @@ public sealed interface RemoteWorkspaceIntent { public val label: String, public val sessionId: String, ) : RemoteWorkspaceIntent + public data object RetryDownload : RemoteWorkspaceIntent public data class DownloadSaved public constructor(public val reference: String) : RemoteWorkspaceIntent public data class DownloadSaveFailed public constructor(public val reference: String) : RemoteWorkspaceIntent public data object DismissPreview : RemoteWorkspaceIntent diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStore.kt index 6b09fd3081..df49d00f03 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStore.kt @@ -18,12 +18,21 @@ public data class RuntimeFilesUiState public constructor( public val directory: String, public val entries: List, public val hasMore: Boolean, public val file: String?, public val content: String, public val busy: Boolean, public val failed: Boolean, public val sort: RuntimeFileSort, + public val completedOperation: Long, + /** Unlocalized diagnostic supplied by the controlled host; null uses the surface fallback. */ + public val errorDetail: String?, ) { + /** Stable host error code; native surfaces own the localized explanation. */ + public val saveConflict: Boolean get() = failed && errorDetail?.startsWith("FILE_CONFLICT:") == true + public constructor(directory: String, entries: List, hasMore: Boolean, file: String?, content: String, busy: Boolean, failed: Boolean, sort: RuntimeFileSort, completedOperation: Long) : this(directory, entries, hasMore, file, content, busy, failed, sort, completedOperation, null) + public constructor(directory: String, entries: List, hasMore: Boolean, file: String?, content: String, busy: Boolean, failed: Boolean, sort: RuntimeFileSort) : this(directory, entries, hasMore, file, content, busy, failed, sort, 0) public constructor(directory: String, entries: List, hasMore: Boolean, file: String?, content: String, busy: Boolean, failed: Boolean) : this(directory, entries, hasMore, file, content, busy, failed, RuntimeFileSort.NAME_ASC) } @Serializable private data class FilesHostResult(override val resp: String? = null, override val message: String? = null, - val ok: Boolean = false, val value: JsonElement = JsonNull) : CommandStatus + val ok: Boolean = false, val value: JsonElement = JsonNull, val error: String? = null) : CommandStatus + +private class FilesHostFailure(val detail: String?) : Exception() internal class RuntimeFilesStore(private val scope: CoroutineScope, private val transport: RemoteCommandTransport) { private val mutable = MutableStateFlow(RuntimeFilesUiState("", emptyList(), false, null, "", false, false)) @@ -40,18 +49,18 @@ internal class RuntimeFilesStore(private val scope: CoroutineScope, private val else args["remoteConnectionId"] = JsonPrimitive(connectionId.orEmpty()) val response = transport.send(RemoteCommand(cmd = "host_invoke", command = command, args = buildJsonObject { put("request", JsonObject(args)) })) - check(response.ok) { "Runtime filesystem operation failed" } + if (!response.ok) throw FilesHostFailure(response.error?.takeIf { it.isNotBlank() }) return response.value } private fun run(operation: suspend (Long) -> Unit) { if (mutable.value.busy) return val ticket = epoch - mutable.value = mutable.value.copy(busy = true, failed = false) + mutable.value = mutable.value.copy(busy = true, failed = false, errorDetail = null) job = scope.launch { try { operation(ticket) } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { if (ticket == epoch) mutable.value = mutable.value.copy(failed = true) } - finally { if (ticket == epoch) mutable.value = mutable.value.copy(busy = false) } + catch (error: Throwable) { if (ticket == epoch) mutable.value = mutable.value.copy(failed = true, errorDetail = (error as? FilesHostFailure)?.detail) } + finally { if (ticket == epoch) mutable.value = mutable.value.copy(busy = false, completedOperation = mutable.value.completedOperation + 1) } } } fun browse(path: String, rootPath: String, remoteConnectionId: String?, append: Boolean, sort: RuntimeFileSort = mutable.value.sort) { @@ -59,21 +68,55 @@ internal class RuntimeFilesStore(private val scope: CoroutineScope, private val stop(); root = rootPath; connectionId = remoteConnectionId mutable.value = RuntimeFilesUiState("", emptyList(), false, null, "", false, false) } - run { ticket -> - val page = invoke("get_directory_children_paginated", buildJsonObject { - put("path", path); put("offset", if (append) mutable.value.entries.size else 0); put("limit", 100) - put("sortBy", if (sort == RuntimeFileSort.NAME_ASC || sort == RuntimeFileSort.NAME_DESC) "name" else "modified") - put("sortOrder", if (sort == RuntimeFileSort.NAME_ASC || sort == RuntimeFileSort.MODIFIED_ASC) "asc" else "desc") - }).jsonObject - val entries = page.getValue("children").jsonArray.map { child -> - val item = child.jsonObject - RuntimeFileUiState(item.getValue("path").jsonPrimitive.content, item.getValue("name").jsonPrimitive.content, - item.getValue("isDirectory").jsonPrimitive.boolean) - } - if (ticket == epoch) mutable.value = mutable.value.copy(directory = path, sort = sort, - entries = if (append) mutable.value.entries + entries else entries, - hasMore = page.getValue("hasMore").jsonPrimitive.boolean, file = null, content = "") + run { ticket -> loadDirectory(ticket, path, append, sort) } + } + private suspend fun loadDirectory(ticket: Long, path: String, append: Boolean, sort: RuntimeFileSort) { + val page = invoke("get_directory_children_paginated", buildJsonObject { + put("path", path); put("offset", if (append) mutable.value.entries.size else 0); put("limit", 100) + put("sortBy", if (sort == RuntimeFileSort.NAME_ASC || sort == RuntimeFileSort.NAME_DESC) "name" else "modified") + put("sortOrder", if (sort == RuntimeFileSort.NAME_ASC || sort == RuntimeFileSort.MODIFIED_ASC) "asc" else "desc") + }).jsonObject + val entries = page.getValue("children").jsonArray.map { child -> + val item = child.jsonObject + RuntimeFileUiState(item.getValue("path").jsonPrimitive.content, item.getValue("name").jsonPrimitive.content, + item.getValue("isDirectory").jsonPrimitive.boolean) } + if (ticket == epoch) mutable.value = mutable.value.copy(directory = path, sort = sort, + entries = if (append) mutable.value.entries + entries else entries, + hasMore = page.getValue("hasMore").jsonPrimitive.boolean, file = null, content = "") + } + private suspend fun refreshDirectory(ticket: Long) { + if (ticket != epoch) return + val current = mutable.value + loadDirectory(ticket, current.directory, false, current.sort) + } + private fun entryPath(name: String): String { + require(name.isNotBlank()) { "A file name is required" } + val directory = mutable.value.directory + check(directory.isNotEmpty()) { "No directory is open" } + return if (name.startsWith('/')) name else directory.trimEnd('/') + "/" + name + } + fun createEntry(name: String, directory: Boolean) = run { ticket -> + val path = entryPath(name) + if (directory) invoke("create_directory", buildJsonObject { put("path", path) }) + else invoke("write_file_content", buildJsonObject { + put("filePath", path); put("workspacePath", root); put("content", ""); put("expectedHash", "") + }) + refreshDirectory(ticket) + } + fun renameEntry(path: String, name: String) = run { ticket -> + check(mutable.value.entries.any { it.path == path }) { "File entry is no longer available" } + val destination = entryPath(name) + invoke("rename_file", buildJsonObject { put("oldPath", path); put("newPath", destination) }) + refreshDirectory(ticket) + } + fun deleteEntry(path: String) = run { ticket -> + val entry = mutable.value.entries.firstOrNull { it.path == path } ?: error("File entry is no longer available") + invoke(if (entry.directory) "delete_directory" else "delete_file", buildJsonObject { + put("path", path) + if (entry.directory) put("recursive", false) + }) + refreshDirectory(ticket) } fun sort(value: RuntimeFileSort) { if (mutable.value.directory.isNotEmpty()) browse(mutable.value.directory, root, connectionId, false, value) } fun closeFile() { if (!mutable.value.busy) { originalHash = null; mutable.value = mutable.value.copy(file = null, content = "") } } @@ -88,6 +131,12 @@ internal class RuntimeFilesStore(private val scope: CoroutineScope, private val val digest = ContentHash.sha256(content) if (ticket == epoch) { originalHash = digest; mutable.value = mutable.value.copy(content = content) } } + fun uploadEntry(name: String, source: RuntimeUploadSource) { + val path = try { entryPath(name) } catch (_: Throwable) { + source.close(); mutable.value = mutable.value.copy(failed = true, errorDetail = null); return + } + upload(path, source) + } fun upload(path: String, source: RuntimeUploadSource) { if (mutable.value.busy) { source.close(); return } var closed = false @@ -127,7 +176,9 @@ internal class RuntimeFilesStore(private val scope: CoroutineScope, private val catch (cancelled: CancellationException) { throw cancelled } catch (error: Throwable) { status = call("status"); if (status["completed"]?.jsonPrimitive?.boolean != true) throw error } } - check(status.getValue("completed").jsonPrimitive.boolean) { "Runtime upload is incomplete" } + check(status.getValue("completed").jsonPrimitive.boolean && + status.getValue("nextOffset").jsonPrimitive.long == source.size) { "Runtime upload is incomplete" } + refreshDirectory(ticket) } finally { withContext(NonCancellable + Dispatchers.Default) { closeSource() } } } job?.invokeOnCompletion { closeSource() } diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStore.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStore.kt index 8dbf330f10..026464203f 100644 --- a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStore.kt +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStore.kt @@ -2,6 +2,7 @@ package com.openbitfun.mobile.core.feature.workspace import com.openbitfun.mobile.core.persistence.RelayStreamStore import com.openbitfun.mobile.core.feature.relay.PersistentSessionReplica +import com.openbitfun.mobile.core.protocol.isError import com.openbitfun.mobile.core.protocol.CommandStatus import com.openbitfun.mobile.core.protocol.RemoteCommand import com.openbitfun.mobile.core.protocol.RelayJson @@ -14,7 +15,9 @@ import kotlinx.serialization.json.* public data class RuntimeTerminalUiState public constructor( public val sessionId: String?, public val output: String, public val busy: Boolean, public val failed: Boolean, public val revision: Long, public val chunk: String, public val reset: Boolean, + public val errorDetail: String?, ) { + public constructor(sessionId: String?, output: String, busy: Boolean, failed: Boolean, revision: Long, chunk: String, reset: Boolean) : this(sessionId, output, busy, failed, revision, chunk, reset, null) public constructor(sessionId: String?, output: String, busy: Boolean, failed: Boolean) : this(sessionId, output, busy, failed, 0, "", true) } @@ -32,16 +35,21 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v private var resizeJob: Job? = null private var pendingInput = "" private var pendingSize: Pair? = null + private var historyFailureActive = false + private fun failHistory(error: Throwable) { historyFailureActive = true; mutable.value = mutable.value.copy(failed = true, errorDetail = error.message) } + private fun failAction(error: Throwable) { historyFailureActive = false; mutable.value = mutable.value.copy(failed = true, errorDetail = error.message) } private var epoch = 0L + private var location: Pair? = null private suspend fun invoke(command: String, args: JsonObject, structured: Boolean = true): JsonElement { val result = transport.send(RemoteCommand(cmd = "host_invoke", command = command, args = if (structured) buildJsonObject { put("request", args) } else args)) - check(result.ok) { "Host terminal operation failed" } + check(result.ok && !result.isError) { result.error?.takeIf { it.isNotBlank() } ?: result.message?.takeIf { it.isNotBlank() } ?: "Host terminal operation failed" } return result.value } fun open(path: String, connectionId: String?) { if (mutable.value.busy || mutable.value.sessionId != null) return - mutable.value = mutable.value.copy(busy = true, failed = false) + location = path to connectionId + mutable.value = mutable.value.copy(busy = true, failed = false, errorDetail = null) val ticket = epoch action = scope.launch { try { @@ -54,10 +62,12 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v mutable.value = RuntimeTerminalUiState(id, "", false, false) observe(id) } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { if (ticket == epoch) mutable.value = mutable.value.copy(busy = false, failed = true) } + catch (error: Throwable) { if (ticket == epoch) { failAction(error); mutable.value = mutable.value.copy(busy = false) } } } } private fun observe(id: String) { + val ticket = epoch + fun isCurrent(): Boolean = ticket == epoch && mutable.value.sessionId == id owner?.cancel() owner = scope.launch { try { @@ -68,17 +78,25 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v var offset = 0L suspend fun refresh() { do { + if (!isCurrent()) return val page = invoke("terminal_get_history", buildJsonObject { put("sessionId", id); put("afterOffset", offset) }, false).jsonObject + if (!isCurrent()) return val next = page.getValue("nextOffset").jsonPrimitive.long val cursor = page.getValue("cursor").jsonPrimitive.long val truncated = page["truncated"]?.jsonPrimitive?.boolean == true + check(next >= 0 && cursor >= next) { "Invalid terminal history cursor" } check(next >= offset || truncated) { "Terminal cursor moved backwards" } + check(next > offset || (truncated && next < offset) || next == cursor) { "Terminal history made no progress" } val data = page.getValue("data").jsonPrimitive.content + if (historyFailureActive) { + historyFailureActive = false + mutable.value = mutable.value.copy(failed = false, errorDetail = null) + } if (data.isNotEmpty() || truncated) { // Local replay is bounded like the runtime ring; live rendering receives only this delta. var replay = ((if (truncated) "" else mutable.value.output) + data).takeLast(4 * 1024 * 1024) if (replay.firstOrNull()?.isLowSurrogate() == true) replay = replay.drop(1) - mutable.value = mutable.value.copy(output = replay, revision = mutable.value.revision + 1, chunk = data, reset = truncated, failed = false) + mutable.value = mutable.value.copy(output = replay, revision = mutable.value.revision + 1, chunk = data, reset = truncated) } offset = next } while (next < cursor) @@ -88,15 +106,15 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v launch { for (request in refreshRequests) { try { refresh() } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { mutable.value = mutable.value.copy(failed = true) } + catch (error: Throwable) { if (isCurrent()) failHistory(error) } } } - source.subscribe("terminal-$id", replica, { mutable.value = mutable.value.copy(failed = true) }, { - if (!caughtUp) { caughtUp = true; refreshRequests.trySend(Unit) } + source.subscribe("terminal-$id", replica, { error -> if (isCurrent()) failHistory(error) }, { + if (isCurrent() && !caughtUp) { caughtUp = true; refreshRequests.trySend(Unit) } }).collect { event -> - if (caughtUp && event["event"]?.jsonPrimitive?.content in setOf("terminal-output", "relay://session-resumed")) refreshRequests.trySend(Unit) + if (isCurrent() && caughtUp && event["event"]?.jsonPrimitive?.content in setOf("terminal-output", "relay://session-resumed")) refreshRequests.trySend(Unit) } } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { mutable.value = mutable.value.copy(failed = true) } + catch (error: Throwable) { if (isCurrent()) failHistory(error) } } } fun write(data: String) { @@ -111,7 +129,7 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v val batch = pendingInput; pendingInput = "" try { invoke("terminal_write", buildJsonObject { put("sessionId", id); put("data", batch) }) } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { if (ticket == epoch) { pendingInput = ""; mutable.value = mutable.value.copy(failed = true) }; break } + catch (error: Throwable) { if (ticket == epoch) { pendingInput = ""; failAction(error) }; break } } } } @@ -126,7 +144,7 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v val size = pendingSize!!; pendingSize = null try { invoke("terminal_resize", buildJsonObject { put("sessionId", id); put("cols", size.first); put("rows", size.second) }) } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { if (ticket == epoch) mutable.value = mutable.value.copy(failed = true); break } + catch (error: Throwable) { if (ticket == epoch) failAction(error); break } } } } @@ -134,15 +152,22 @@ internal class RuntimeTerminalStore(private val scope: CoroutineScope, private v val id = mutable.value.sessionId ?: return if (mutable.value.busy) return mutable.value = mutable.value.copy(busy = true) + val ticket = epoch action = scope.launch { try { invoke("terminal_close", buildJsonObject { put("sessionId", id) }) - stop() + if (ticket == epoch) stop(clearLocation = false) } catch (cancelled: CancellationException) { throw cancelled } - catch (_: Throwable) { mutable.value = mutable.value.copy(busy = false, failed = true) } + catch (error: Throwable) { if (ticket == epoch) { failAction(error); mutable.value = mutable.value.copy(busy = false) } } } } - fun stop() { + fun reopen() { + val previous = location ?: return + open(previous.first, previous.second) + } + fun stop(clearLocation: Boolean = true) { + historyFailureActive = false + if (clearLocation) location = null epoch++; owner?.cancel(); action?.cancel(); inputJob?.cancel(); resizeJob?.cancel(); pendingInput = ""; pendingSize = null mutable.value = RuntimeTerminalUiState(null, "", false, false) } diff --git a/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogUiState.kt b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogUiState.kt new file mode 100644 index 0000000000..f12e225dbb --- /dev/null +++ b/src/apps/mobile/shared/core-feature/src/commonMain/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogUiState.kt @@ -0,0 +1,52 @@ +package com.openbitfun.mobile.core.feature.workspace + +import com.openbitfun.mobile.core.domain.RecentWorkspace +import com.openbitfun.mobile.core.domain.WorkspaceAssistant +import com.openbitfun.mobile.core.protocol.RecentWorkspaceListResponse + +public enum class WorkspaceCatalogSource { OPENED, RECENT } + +/** Sidebar membership is separate from the recent history offered by the picker. */ +public data class WorkspaceCatalogUiState public constructor( + public val workspaces: List, + public val source: WorkspaceCatalogSource, + public val recentWorkspaces: List, +) { + public constructor(workspaces: List, source: WorkspaceCatalogSource) : this(workspaces, source, workspaces) +} + +internal fun RecentWorkspaceListResponse.sidebarCatalog( + assistants: List, +): WorkspaceCatalogUiState { + val rows = (openedWorkspaces ?: workspaces).map { item -> + RecentWorkspace(item.path.orEmpty(), item.name.orEmpty(), item.lastOpened, + item.workspaceKind.orEmpty(), item.remoteSshHost, item.remoteConnectionId) + } + return projectWorkspaceCatalog(rows, assistants, openedWorkspaces != null).copy(recentWorkspaces = workspaces.map { item -> + RecentWorkspace(item.path.orEmpty(), item.name.orEmpty(), item.lastOpened, + item.workspaceKind.orEmpty(), item.remoteSshHost, item.remoteConnectionId) + }.filter { it.path.isNotBlank() }) +} + +internal fun projectWorkspaceCatalog( + rows: List, + assistants: List, + opened: Boolean = false, +): WorkspaceCatalogUiState { + val candidates = if (opened) rows else assistants.map { + RecentWorkspace(it.path, it.name, "", "assistant") + } + rows + return WorkspaceCatalogUiState( + candidates.filter { it.path.isNotBlank() }.map { row -> + val assistant = if (row.remoteConnectionId.isNullOrEmpty() && row.remoteSshHost.isNullOrEmpty()) { + assistants.find { it.path == row.path && it.name.isNotBlank() } + } else null + when { + assistant != null -> row.copy(name = assistant.name, kind = "assistant") + row.name.isBlank() -> row.copy(name = row.path.trimEnd('/').substringAfterLast('/')) + else -> row + } + }.distinctBy { Triple(it.remoteConnectionId, it.remoteSshHost, it.path) }, + if (opened) WorkspaceCatalogSource.OPENED else WorkspaceCatalogSource.RECENT, + ) +} diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/account/AccountStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/account/AccountStoreTest.kt index 00dc60fb05..67e01e0dab 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/account/AccountStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/account/AccountStoreTest.kt @@ -21,6 +21,76 @@ import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) class AccountStoreTest { + @Test fun cancelledLoginDirectoryCannotReviveAccountOrPersistSelectedDevice() = runTest { + for (failure in listOf(null, CloudAccountException(CloudAccountFailure.NETWORK), + CloudAccountException(CloudAccountFailure.AUTHENTICATION), IllegalStateException("Late failure"))) { + val secure = MemorySecureStore(); val backend = FakeAccountBackend() + var pending: kotlin.coroutines.Continuation? = null + backend.beforeList = { kotlin.coroutines.suspendCoroutine { pending = it } } + val store = AccountStore.create(this, backend, secure, "phone-1", "Android") + store.dispatch(AccountIntent.Login); advanceUntilIdle() + store.dispatch(AccountIntent.Logout) + pending!!.resumeWith(if (failure == null) Result.success(Unit) else Result.failure(failure)) + advanceUntilIdle() + assertIs(store.state.value) + assertNull(secure.read("github_device_session_v1")) + assertNull(store.createSessionStore(this)) + store.stop() + } + } + + @Test fun cancelledRestoreCannotReplaceSignedOutState() = runTest { + for (failure in listOf(null, CloudAccountException(CloudAccountFailure.NETWORK), + CloudAccountException(CloudAccountFailure.AUTHENTICATION))) { + val secure = MemorySecureStore(); val backend = FakeAccountBackend() + val first = AccountStore.create(this, backend, secure, "phone-1", "Android") + first.dispatch(AccountIntent.Login); advanceUntilIdle(); first.stop() + var pending: kotlin.coroutines.Continuation? = null + backend.beforeList = { kotlin.coroutines.suspendCoroutine { pending = it } } + val restored = AccountStore.create(this, backend, secure, "phone-1", "Android") + restored.dispatch(AccountIntent.Restore); advanceUntilIdle() + restored.dispatch(AccountIntent.Logout) + pending!!.resumeWith(if (failure == null) Result.success(Unit) else Result.failure(failure)) + advanceUntilIdle() + assertIs(restored.state.value) + restored.stop() + } + } + + @Test fun cancelledRefreshFailureCannotRestoreSignedOutAccount() = runTest { + for (failure in listOf(CloudAccountException(CloudAccountFailure.NETWORK), + CloudAccountException(CloudAccountFailure.AUTHENTICATION), IllegalStateException("Late failure"))) { + val backend = FakeAccountBackend() + val store = AccountStore.create(this, backend, MemorySecureStore(), "phone-1", "Android") + store.dispatch(AccountIntent.Login); advanceUntilIdle() + var pending: kotlin.coroutines.Continuation? = null + backend.beforeList = { kotlin.coroutines.suspendCoroutine { pending = it } } + store.dispatch(AccountIntent.RefreshDevices); advanceUntilIdle() + store.dispatch(AccountIntent.Logout) + pending!!.resumeWith(Result.failure(failure)); advanceUntilIdle() + assertIs(store.state.value) + store.stop() + } + } + + @Test fun cancelledAuthenticationFailureCannotExpireReplacementLoginWithSameToken() = runTest { + val backend = FakeAccountBackend() + val store = AccountStore.create(this, backend, MemorySecureStore(), "phone-1", "Android") + store.dispatch(AccountIntent.Login); advanceUntilIdle() + var pending: kotlin.coroutines.Continuation? = null + backend.beforeList = { kotlin.coroutines.suspendCoroutine { pending = it } } + store.dispatch(AccountIntent.RefreshDevices); advanceUntilIdle() + store.dispatch(AccountIntent.Logout) + backend.beforeList = null + backend.userId = "replacement-user" + store.dispatch(AccountIntent.Login); advanceUntilIdle() + val replacement = assertIs(store.state.value) + pending!!.resumeWith(Result.failure(CloudAccountException(CloudAccountFailure.AUTHENTICATION))) + advanceUntilIdle() + assertEquals(replacement, store.state.value) + store.stop() + } + @Test fun directoryNotificationsRefreshMembershipWithoutAPeriodicPoll() = runTest { val changes = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 4) val backend = FakeAccountBackend().apply { directoryEvents = changes } @@ -201,6 +271,57 @@ class AccountStoreTest { assertEquals("desktop-1", failed.selectedDeviceId) } + @Test fun coldRestorePublishesIdentityBeforeDirectoryWithoutGrantingTargetAuthority() = runTest { + val secure = MemorySecureStore(); val backend = FakeAccountBackend() + val first = AccountStore.create(this, backend, secure, "phone-1", "Android") + first.dispatch(AccountIntent.Login); advanceUntilIdle(); first.stop() + val saved = secure.read("github_device_session_v1")!! + var pending: kotlin.coroutines.Continuation? = null + backend.beforeList = { kotlin.coroutines.suspendCoroutine { pending = it } } + val restored = AccountStore.create(this, backend, secure, "phone-1", "Android") + restored.dispatch(AccountIntent.Restore); advanceUntilIdle() + val loading = assertIs(restored.state.value) + assertEquals("user-id", loading.userId) + assertTrue(loading.refreshing) + assertTrue(loading.devices.isEmpty()) + assertNull(loading.selectedDeviceId) + assertNull(restored.createSessionStore(this)) + assertNull(restored.createWorkspaceStore(this, "desktop-1")) + pending!!.resumeWith(Result.success(Unit)); advanceUntilIdle() + val ready = assertIs(restored.state.value) + assertFalse(ready.refreshing) + assertEquals("desktop-1", ready.selectedDeviceId) + assertContentEquals(saved, secure.read("github_device_session_v1")) + restored.stop() + } + + @Test fun failedRefreshDoesNotRollBackDeviceSelectedWhileRequestWasPending() = runTest { + for (failure in listOf(CloudAccountException(CloudAccountFailure.TIMEOUT), IllegalStateException("Network unavailable"))) { + val backend = FakeAccountBackend().apply { desktop2Online = true } + val secure = MemorySecureStore() + val store = AccountStore.create(this, backend, secure, "phone-1", "Android") + store.dispatch(AccountIntent.Login); advanceUntilIdle() + var pending: kotlin.coroutines.Continuation? = null + backend.beforeList = { kotlin.coroutines.suspendCoroutine { pending = it } } + store.dispatch(AccountIntent.RefreshDevices); advanceUntilIdle() + store.dispatch(AccountIntent.SelectDevice("desktop-2")) + val selected = assertIs(store.state.value) + assertEquals("desktop-2", selected.selectedDeviceId) + val saved = secure.read("github_device_session_v1")!! + pending!!.resumeWith(Result.failure(failure)); advanceUntilIdle() + val failed = assertIs(store.state.value) + assertEquals(selected.selectedDeviceId, failed.selectedDeviceId) + assertEquals(selected.selectedDeviceName, failed.selectedDeviceName) + assertEquals(selected.devices, failed.devices) + assertFalse(failed.refreshing) + assertEquals(if (failure is CloudAccountException) AccountFailureReason.TIMEOUT else AccountFailureReason.NETWORK, failed.refreshFailure) + assertContentEquals(saved, secure.read("github_device_session_v1")) + store.createSessionStore(this) + assertEquals("desktop-2", backend.transportTargets.last()) + store.stop() + } + } + @Test fun explicitDeviceStoresCoexistWithSelectedTargetStore() = runTest { val backend = FakeAccountBackend() @@ -397,9 +518,9 @@ class AccountStoreTest { store.dispatch(AccountIntent.Login) advanceUntilIdle() - val failed = assertIs(store.state.value) - assertEquals(AccountFailureReason.NETWORK, failed.reason) - assertEquals(AccountFailureStage.DEVICE_LIST, failed.stage) + val ready = assertIs(store.state.value) + assertEquals(AccountFailureReason.NETWORK, ready.refreshFailure) + assertNull(ready.selectedDeviceId) assertTrue(secure.read("github_device_session_v1")?.isNotEmpty() == true) assertEquals(0, secure.deleteCount) } @@ -413,9 +534,17 @@ class AccountStoreTest { store.dispatch(AccountIntent.Login) advanceUntilIdle() - val failed = assertIs(store.state.value) - assertEquals(transportReason.toExpectedReason(), failed.reason) - assertEquals(AccountFailureStage.DEVICE_LIST, failed.stage) + if (transportReason == CloudAccountFailure.AUTHENTICATION) { + val failed = assertIs(store.state.value) + assertEquals(AccountFailureReason.AUTHENTICATION, failed.reason) + assertFalse(failed.canRetry) + } else { + val ready = assertIs(store.state.value) + assertEquals(transportReason.toExpectedReason(), ready.refreshFailure) + assertEquals("user-id", ready.userId) + assertNull(store.createSessionStore(this)) + } + store.stop() } } @@ -427,8 +556,8 @@ class AccountStoreTest { store.dispatch(AccountIntent.Login) advanceUntilIdle() - val failed = assertIs(store.state.value) - assertEquals(AccountFailureStage.DEVICE_LIST, failed.stage) + val readyBeforeRetry = assertIs(store.state.value) + assertEquals(AccountFailureReason.TIMEOUT, readyBeforeRetry.refreshFailure) val stored = secure.read("github_device_session_v1")!!.toList() val writes = secure.writeCount val deletes = secure.deleteCount @@ -493,8 +622,20 @@ class AccountStoreTest { restored.dispatch(AccountIntent.Restore) advanceUntilIdle() - assertEquals(AccountFailureReason.NETWORK, assertIs(restored.state.value).reason) - assertTrue(secure.read("github_device_session_v1")?.isNotEmpty() == true) + val offline = assertIs(restored.state.value) + assertEquals(AccountFailureReason.NETWORK, offline.refreshFailure) + assertEquals("user-id", offline.userId) + assertTrue(offline.devices.isEmpty()) + assertNull(offline.selectedDeviceId) + assertNull(restored.createSessionStore(this)) + val saved = secure.read("github_device_session_v1")!!.toList() + backend.listFailure = null + restored.dispatch(AccountIntent.RefreshDevices); advanceUntilIdle() + val recovered = assertIs(restored.state.value) + assertNull(recovered.refreshFailure) + assertEquals("desktop-1", recovered.selectedDeviceId) + assertEquals(saved, secure.read("github_device_session_v1")!!.toList()) + first.stop(); restored.stop() } } @@ -551,6 +692,7 @@ private class FakeAccountBackend : AccountBackend { var userId = "user-id" var listRequests = 0 + var beforeList: (suspend () -> Unit)? = null var lastLoginRelay = "" var profileResult: com.openbitfun.mobile.core.transport.GitHubProfile? = null var profileLoads = 0 @@ -590,6 +732,7 @@ private class FakeAccountBackend : AccountBackend { override suspend fun listDevices(session: AccountSessionData, selfDeviceId: String): List { listRequests++ + beforeList?.invoke() listThrowable?.let { throw it } listFailure?.let { throw CloudAccountException(it) } // The shape the live account returns: this device, one of the user's diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStoreTest.kt index 98042f13e7..a33aa4c0b9 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/DeviceDirectoryStoreTest.kt @@ -76,6 +76,68 @@ class DeviceDirectoryStoreTest { assertTrue(stillA.sessions.isEmpty()) } + @Test + fun authoritativeCloseRetainsSessionsAndCannotBeUndoneByOfflineCacheHydration() = runTest { + val transport = FakeDeviceTransport("a") + val history = MemoryDirectoryWorkspaces() + val cache = MemoryDirectorySessions() + val store = DeviceDirectoryStore.create(this, CachedDeviceStoreFactory(transport, cache, history)) + store.dispatch(DeviceDirectoryIntent.Sync(listOf(DeviceDirectoryDevice("a", true)))) + store.dispatch(DeviceDirectoryIntent.Expand("a")) + advanceUntilIdle() + store.dispatch(DeviceDirectoryIntent.SetWorkspaceExpanded("a", "/repo-a", true)) + advanceUntilIdle() + assertEquals(listOf("s-a"), store.state.value.device("a")!!.sessions.map { it.id }) + transport.openedJson = "[]" + transport.assistantJson = """[{"path":"/assistant","name":"Assistant"}]""" + store.dispatch(DeviceDirectoryIntent.Retry("a")) + advanceUntilIdle() + val ready = store.state.value.device("a")!! + assertTrue(ready.workspaces.isEmpty()) + assertEquals("OPENED", ready.catalogSource?.name) + assertEquals(listOf("/repo-a"), ready.recentWorkspaces.map { it.path }) + assertEquals(listOf("s-a"), ready.sessions.map { it.id }) + assertTrue(history.byDevice.getValue("a").any { it.path == "/repo-a" }) + store.dispatch(DeviceDirectoryIntent.Sync(listOf(DeviceDirectoryDevice("a", false)))) + store.dispatch(DeviceDirectoryIntent.Expand("a")) + assertTrue(store.state.value.device("a")!!.workspaces.isEmpty()) + assertEquals(listOf("s-a"), store.state.value.device("a")!!.sessions.map { it.id }) + store.stop() + } + + @Test + fun samePathSshDirectoriesLoadIndependentlyAndRefreshOnlyTheirOwnSessions() = runTest { + val transport = FakeDeviceTransport("a") + transport.openedJson = """[{"path":"/shared","remote_connection_id":"ssh-a","remote_ssh_host":"a"},{"path":"/shared","remote_connection_id":"ssh-b","remote_ssh_host":"b"}]""" + transport.sessionJsonByConnection["ssh-a"] = """[{"id":"session-a","agent_type":"code"}]""" + transport.sessionJsonByConnection["ssh-b"] = """[{"id":"session-b","agent_type":"code"}]""" + val sessions = MemoryDirectorySessions() + val store = DeviceDirectoryStore.create(this, CachedDeviceStoreFactory(transport, sessions, MemoryDirectoryWorkspaces())) + store.dispatch(DeviceDirectoryIntent.Sync(listOf(DeviceDirectoryDevice("a", true)))) + store.dispatch(DeviceDirectoryIntent.Expand("a")); advanceUntilIdle() + val gateA = CompletableDeferred(); val gateB = CompletableDeferred() + transport.sessionGateByConnection["ssh-a"] = gateA + transport.sessionGateByConnection["ssh-b"] = gateB + store.dispatch(DeviceDirectoryIntent.SetWorkspaceExpanded("a", "/shared", true, "ssh-a", "a")) + store.dispatch(DeviceDirectoryIntent.SetWorkspaceExpanded("a", "/shared", true, "ssh-b", "b")) + runCurrent() + assertEquals(2, transport.commands.count { it.cmd == "list_sessions" }) + gateB.complete(Unit); runCurrent() + assertEquals(WorkspaceDirectoryStatus.READY, store.state.value.device("a")!!.workspace("/shared", "ssh-b", "b")!!.status) + assertEquals(WorkspaceDirectoryStatus.LOADING, store.state.value.device("a")!!.workspace("/shared", "ssh-a", "a")!!.status) + gateA.complete(Unit); advanceUntilIdle() + val ready = store.state.value.device("a")!! + assertEquals(listOf("session-a"), ready.sessionsForWorkspace("/shared", "ssh-a", "a").map { it.id }) + assertEquals(listOf("session-b"), ready.sessionsForWorkspace("/shared", "ssh-b", "b").map { it.id }) + assertTrue(ready.sessionsForWorkspace("/shared", null, null).isEmpty()) + assertEquals(setOf("ssh-a", "ssh-b"), sessions.byDevice.getValue("a").map { it.workspaceIdentity?.remoteConnectionId }.toSet()) + transport.sessionJsonByConnection["ssh-a"] = "[]" + store.dispatch(DeviceDirectoryIntent.RetryWorkspace("a", "/shared", "ssh-a", "a")); advanceUntilIdle() + assertEquals(listOf("session-b"), store.state.value.device("a")!!.sessions.map { it.id }) + assertEquals("a", transport.commands.last { it.cmd == "list_sessions" }.remoteSshHost) + store.stop() + } + @Test fun duplicateLoadCollapsesToOneFetch() = runTest { val transport = FakeDeviceTransport("a") @@ -243,8 +305,8 @@ class DeviceDirectoryStoreTest { advanceUntilIdle() val workspaces = store.state.value.device("a")!!.workspaces - assertEquals(listOf("/repo-a", "/assistant-a"), workspaces.map { it.path }) - assertEquals("assistant", workspaces.last().kind) + assertEquals(listOf("/assistant-a", "/repo-a"), workspaces.map { it.path }) + assertEquals("assistant", workspaces.first().kind) } @Test @@ -456,7 +518,7 @@ class DeviceDirectoryStoreTest { } @Test - fun legacySessionCacheInfersWorkspaceCatalogAfterUpgrade() = runTest { + fun legacySessionCacheIsRetainedWithoutInventingOpenedWorkspaces() = runTest { val transport = FakeDeviceTransport("a") val cachedSessions = MemoryDirectorySessions().apply { byDevice["a"] = listOf( @@ -477,9 +539,8 @@ class DeviceDirectoryStoreTest { val cached = store.state.value.device("a")!! assertEquals(DeviceDirectoryStatus.CACHED, cached.status) - assertEquals(listOf("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/legacy/repo/"), cached.workspaces.map { it.path }) - assertEquals("Legacy repo", cached.workspaces.single().name) - assertEquals(WorkspaceDirectoryStatus.READY, cached.workspace("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/legacy/repo")?.status) + assertTrue(cached.workspaces.isEmpty()) + assertEquals(listOf("legacy-session"), cached.sessions.map { it.id }) } } @@ -506,9 +567,12 @@ private class FakeDeviceTransport(private val deviceId: String) : RemoteCommandT var workspaceFailure: RelayFailure? = null var sessionGate: CompletableDeferred? = null var workspaceGate: CompletableDeferred? = null + val sessionJsonByConnection = mutableMapOf() + val sessionGateByConnection = mutableMapOf>() var sessionJson: String = """[{"id":"s-$deviceId","title":"Session $deviceId","agent_type":"code"}]""" var assistantJson: String = "[]" + var openedJson: String = "null" override suspend fun send( deserializer: DeserializationStrategy, @@ -520,7 +584,7 @@ private class FakeDeviceTransport(private val deviceId: String) : RemoteCommandT "list_recent_workspaces" -> { workspaceFailure?.let { throw RelayTransportException(it) } workspaceGate?.await() - """{"resp":"ok","workspaces":[{"path":"/repo-$deviceId","name":"Repo $deviceId","last_opened":"2026-08-09","workspace_kind":"local"}]}""" + """{"resp":"ok","workspaces":[{"path":"/repo-$deviceId","name":"Repo $deviceId","last_opened":"2026-08-09","workspace_kind":"local"}],"opened_workspaces":$openedJson}""" } "list_assistants" -> """{"resp":"ok","assistants":$assistantJson}""" "get_workspace_info" -> @@ -528,7 +592,9 @@ private class FakeDeviceTransport(private val deviceId: String) : RemoteCommandT "list_sessions" -> { sessionFailure?.let { throw RelayTransportException(it) } sessionGate?.await() - """{"resp":"ok","has_more":false,"sessions":$sessionJson}""" + sessionGateByConnection[command.remoteConnectionId]?.await() + val selectedJson = sessionJsonByConnection[command.remoteConnectionId] ?: sessionJson + """{"resp":"ok","has_more":false,"sessions":$selectedJson}""" } "get_model_catalog" -> """{"resp":"ok"}""" else -> error("Unexpected command ${command.cmd}") diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/WorkspaceSessionPersistenceTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/WorkspaceSessionPersistenceTest.kt new file mode 100644 index 0000000000..e81d964419 --- /dev/null +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/directory/WorkspaceSessionPersistenceTest.kt @@ -0,0 +1,29 @@ +package com.openbitfun.mobile.core.feature.directory + +import com.openbitfun.mobile.core.persistence.PersistedRemoteSession +import com.openbitfun.mobile.core.persistence.PersistedWorkspaceIdentity +import com.openbitfun.mobile.core.protocol.RelayJson +import kotlinx.serialization.encodeToString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class WorkspaceSessionPersistenceTest { + @Test + fun legacyRecordsRemainReadableWithoutInventingLocalOwnership() { + val legacy = RelayJson.decodeFromString( + """{"sessionId":"old","workspacePath":"/repo","workspaceName":"Project","pendingConfirmed":true}""", + ) + assertNull(legacy.workspaceIdentity) + val restored = RelayJson.decodeFromString(RelayJson.encodeToString(legacy)) + assertEquals(legacy, restored) + } + + @Test + fun explicitLocalAndSshIdentitySurviveCacheRoundTrip() { + for (identity in listOf(PersistedWorkspaceIdentity("/repo"), PersistedWorkspaceIdentity("/repo", "ssh", "host"))) { + val session = PersistedRemoteSession(sessionId = "s", workspacePath = "/repo", workspaceIdentity = identity) + assertEquals(session, RelayJson.decodeFromString(RelayJson.encodeToString(session))) + } + } +} diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStoreTest.kt index 11b3a03621..48b805b7de 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/PermissionMailboxStoreTest.kt @@ -3,10 +3,12 @@ package com.openbitfun.mobile.core.feature.session import com.openbitfun.mobile.core.protocol.* import com.openbitfun.mobile.core.transport.* import kotlinx.coroutines.test.* +import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.json.* import kotlin.test.* +@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class PermissionMailboxStoreTest { @Test fun requestsWithoutToolCallsRemainAnswerableAndEditsUseRequestIdentity() = runTest { val host = Host() @@ -16,6 +18,12 @@ class PermissionMailboxStoreTest { assertEquals(listOf("request-id"), latest.requests.map { it.requestId }) assertNull(latest.requests.single().toolCallId) assertEquals("question-id", latest.questions.single().id) + assertTrue(latest.ownsToolInteraction("question-id")) + assertFalse(latest.ownsToolInteraction("request-id")) + assertFalse(latest.ownsToolInteraction("")) + val linked = latest.copy(requests = listOf(latest.requests.single().copy(toolCallId = "permission-tool"))) + assertTrue(linked.ownsToolInteraction("permission-tool")) + assertFalse(linked.ownsToolInteraction("another-tool")) assertEquals("Continue?", latest.questions.single().question) store.respond("request-id", true, """{"path":"/new"}"""); advanceUntilIdle() val reply = host.commands.first { it.command == "respond_permission" }.args!!.jsonObject.getValue("request").jsonObject @@ -32,12 +40,181 @@ class PermissionMailboxStoreTest { store.select("session"); advanceUntilIdle(); store.respond("request-id", true, "[]"); advanceUntilIdle() assertTrue(latest.failed); assertTrue(host.commands.none { it.command == "respond_permission" }) } + @Test fun rejectionIgnoresMalformedApprovalDraft() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + store.select("session"); advanceUntilIdle() + store.respond("request-id", false, "{invalid"); advanceUntilIdle() + val reply = host.commands.single { it.command == "respond_permission" } + .args!!.jsonObject.getValue("request").jsonObject + assertEquals("reject", reply.getValue("reply").jsonPrimitive.content) + assertFalse("updatedInput" in reply) + assertFalse(latest.failed) + } + + @Test fun questionFailureIsVisibleAndCanBeRetriedWithoutSendingUnknownIds() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + store.select("session"); advanceUntilIdle() + store.startQuestion("stale-question"); advanceUntilIdle() + assertTrue(host.commands.none { it.cmd == "start_question_interaction" }) + host.questionError = true + store.startQuestion("question-id"); advanceUntilIdle() + assertTrue(latest.failed) + host.questionError = false + store.startQuestion("question-id"); advanceUntilIdle() + store.startQuestion("question-id"); advanceUntilIdle() + assertFalse(latest.failed) + assertEquals(2, host.commands.count { it.cmd == "start_question_interaction" }) + store.select(null) + store.startQuestion("question-id"); advanceUntilIdle() + assertEquals(2, host.commands.count { it.cmd == "start_question_interaction" }) + } + @Test fun replyRemainsBusyUntilAuthoritativeRefreshAndCannotBeSubmittedTwice() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + store.select("session"); advanceUntilIdle() + val refreshGate = CompletableDeferred() + host.refreshGate = refreshGate + store.respond("request-id", true, null); runCurrent() + assertTrue(latest.busy) + store.respond("request-id", false, null); runCurrent() + assertEquals(1, host.commands.count { it.command == "respond_permission" }) + refreshGate.complete(Unit); advanceUntilIdle() + assertFalse(latest.busy) + } + + @Test fun leavingSessionCancelsPendingReplyRefreshWithoutRestoringOldRows() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + store.select("session"); advanceUntilIdle() + val refreshGate = CompletableDeferred() + host.refreshGate = refreshGate + store.respond("request-id", true, null); runCurrent() + assertTrue(latest.busy) + store.select(null) + refreshGate.complete(Unit); advanceUntilIdle() + assertFalse(latest.busy) + assertTrue(latest.requests.isEmpty()) + assertFalse(latest.failed) + } + + @Test fun invalidatedInflightSnapshotNeverReplacesTheVisibleMailbox() = runTest { + val host = Host() + val published = mutableListOf() + val store = PermissionMailboxStore(this, host) { published += it } + store.select("session"); advanceUntilIdle() + val gate = CompletableDeferred() + host.refreshGate = gate + host.requestId = "stale-request" + store.invalidate(); runCurrent() + host.requestId = "current-request" + store.invalidate() + gate.complete(Unit); advanceUntilIdle() + assertEquals("current-request", published.last().requests.single().requestId) + assertTrue(published.none { state -> state.requests.any { it.requestId == "stale-request" } }) + assertEquals(3, host.commands.count { it.command == "get_session_interaction_mailbox" }) + } + + @Test fun questionAnswerFailureRetainsMailboxAndSuccessfulRetryRefreshesIt() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + store.select("session"); advanceUntilIdle() + val answers = buildJsonObject { put("0", "Yes") } + assertFalse(store.answer("other", "question-id", answers)) + host.questionError = true + assertTrue(store.answer("session", "question-id", answers)); advanceUntilIdle() + assertTrue(latest.failed) + assertEquals("question-id", latest.questions.single().id) + host.questionError = false + val gate = CompletableDeferred() + host.refreshGate = gate + assertTrue(store.answer("session", "question-id", answers)); runCurrent() + assertTrue(latest.busy) + assertTrue(store.answer("session", "question-id", answers)); runCurrent() + assertEquals(2, host.commands.count { it.cmd == "answer_question" }) + gate.complete(Unit); advanceUntilIdle() + assertFalse(latest.busy); assertFalse(latest.failed) + assertTrue(latest.questions.isEmpty()) + assertTrue(latest.ownsToolInteraction("question-id")) + assertEquals(2, host.commands.count { it.command == "get_session_interaction_mailbox" }) + store.select(null) + assertFalse(latest.ownsToolInteraction("question-id")) + } + + @Test fun reconnectReplacesQueueAndIgnoresRequestsAnsweredByAnotherController() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + host.requestIds = listOf("one", "two") + store.select("session"); advanceUntilIdle() + host.ok = false + store.invalidate(); advanceUntilIdle() + assertTrue(latest.failed) + assertEquals(listOf("one", "two"), latest.requests.map { it.requestId }) + host.requestIds = listOf("two"); host.ok = true + store.invalidate(); advanceUntilIdle() + assertFalse(latest.failed) + assertEquals(listOf("two"), latest.requests.map { it.requestId }) + store.respond("one", true, null); advanceUntilIdle() + assertTrue(host.commands.none { it.command == "respond_permission" }) + host.requestIds = emptyList() + store.respond("two", false, null); advanceUntilIdle() + val reply = host.commands.single { it.command == "respond_permission" } + .args!!.jsonObject.getValue("request").jsonObject + assertEquals("two", reply.getValue("requestId").jsonPrimitive.content) + assertEquals("reject", reply.getValue("reply").jsonPrimitive.content) + assertTrue(latest.requests.isEmpty()) + } + + @Test fun invalidationDuringFailedReadStillFetchesCurrentMailbox() = runTest { + val host = Host() + var latest = PermissionMailboxUiState(emptyList(), false, false) + val store = PermissionMailboxStore(this, host) { latest = it } + store.select("session"); advanceUntilIdle() + val gate = CompletableDeferred() + host.refreshGate = gate + store.invalidate(); runCurrent() + host.refreshGate = null + host.requestId = "current-request" + store.invalidate() + gate.completeExceptionally(IllegalStateException("Old connection closed")) + advanceUntilIdle() + assertEquals("current-request", latest.requests.single().requestId) + assertFalse(latest.failed) + assertEquals(3, host.commands.count { it.command == "get_session_interaction_mailbox" }) + } + private class Host : RemoteCommandTransport { + var requestId = "request-id" + var requestIds: List? = null var ok = true + var questionError = false + var answered = false + var refreshGate: CompletableDeferred? = null val commands = mutableListOf() override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { commands += command - val value = if (command.command == "get_session_interaction_mailbox") """{"sessionId":"session","permissions":{"requests":[{"requestId":"request-id","sessionId":"session","action":"write","resources":["/file"],"source":{"identity":"agent"}},{"requestId":"other","sessionId":"other","action":"write"}]},"userQuestions":{"questions":[{"sessionId":"session","toolId":"question-id","questions":{"questions":[{"question":"Continue?"}]}}]}}""" else "null" + val snapshotRequestId = requestId + val snapshotRequests = (requestIds ?: listOf(snapshotRequestId)).joinToString(",") { + """{"requestId":"$it","sessionId":"session","action":"write","resources":["/file"],"source":{"identity":"agent"}}""" + } + if (command.command == "get_session_interaction_mailbox") refreshGate?.await() + if (command.cmd == "start_question_interaction" || command.cmd == "answer_question") { + if (command.cmd == "answer_question" && !questionError) answered = true + return RelayJson.decodeFromString(deserializer, + if (questionError) """{"resp":"error","message":"Question is unavailable"}""" + else """{"resp":"ok"}""") + } + val pendingQuestions = if (answered) "[]" else """[{"sessionId":"session","toolId":"question-id","questions":{"questions":[{"question":"Continue?"}]}}]""" + val foreignRequest = """{"requestId":"other","sessionId":"other","action":"write"}""" + val allRequests = listOf(snapshotRequests, foreignRequest).filter { it.isNotEmpty() }.joinToString(",") + val value = if (command.command == "get_session_interaction_mailbox") """{"sessionId":"session","permissions":{"requests":[$allRequests]},"userQuestions":{"questions":$pendingQuestions}}""" else "null" return RelayJson.decodeFromString(deserializer, """{"resp":"host_invoke_result","ok":$ok,"value":$value}""") } } diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentationTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentationTest.kt new file mode 100644 index 0000000000..50545edb1a --- /dev/null +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RecentSessionsPresentationTest.kt @@ -0,0 +1,23 @@ +package com.openbitfun.mobile.core.feature.session + +import kotlin.test.Test +import kotlin.test.assertEquals + +class RecentSessionsPresentationTest { + @Test fun recentEntriesExcludeArchivedAndUseCreatedTimeWhenUpdateIsInvalid() { + fun row(id: String, updated: String, created: String = "", status: String = "idle") = + RecentSessionUiState(id, status, updated, created) + val source = listOf( + row("older", "2026-09-10T00:00:00Z"), + row("archived", "2026-09-16T00:00:00Z", status = "archived"), + row("fallback", "invalid", "2026-09-14T00:00:00Z"), + row("newest", "2026-09-15T00:00:00Z"), + row("newest", "2026-09-15T00:00:00Z"), + row("", "2026-09-16T00:00:00Z"), + row("unknown", "invalid"), + ) + assertEquals(listOf("newest", "fallback", "older"), RecentSessionsPresentation.sessionIds(source)) + assertEquals("older", source.first().id) + assertEquals(emptyList(), RecentSessionsPresentation.sessionIds(emptyList())) + } +} diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStoreTest.kt index 2ba8c02b7c..1ec679f2f8 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/RemoteSessionStoreTest.kt @@ -35,11 +35,70 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull +import kotlin.test.assertNotNull import kotlin.test.assertIs import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) class RemoteSessionStoreTest { + @Test + fun firstHistoryReadDoesNotPublishAnEmptyOrPartialConversation() = runTest { + val transport = FakeSessionTransport().apply { + initialHistoryGate = CompletableDeferred() + initialEvents = listOf(richRecord("s-code", "first", 0, 1, "completed", "answer")) + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Load); runCurrent() + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + assertNull((store.state.value as? RemoteSessionUiState.Ready)?.timeline) + transport.initialHistoryGate!!.complete(Unit); runCurrent() + val ready = assertIs(store.state.value) + assertEquals("s-code", ready.selectedSessionId) + assertTrue(ready.timeline!!.persistedMessages.isNotEmpty()) + assertFalse(ready.busy) + store.stop() + } + + @Test + fun anEmptySessionIsEmptyOnlyAfterTheHistoryReadCompletes() = runTest { + val transport = FakeSessionTransport().apply { initialHistoryGate = CompletableDeferred() } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + assertFalse(store.state.value is RemoteSessionUiState.Ready) + transport.initialHistoryGate!!.complete(Unit); runCurrent() + val ready = assertIs(store.state.value) + assertTrue(ready.timeline!!.persistedMessages.isEmpty()) + assertFalse(ready.busy) + store.stop() + } + + @Test + fun initialHistoryCanRecoverAfterTransientStreamFailure() = runTest { + val transport = FakeSessionTransport().apply { initialHistoryGate = CompletableDeferred() } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + transport.streamError!!.invoke(IllegalStateException("Temporary disconnect")); runCurrent() + assertFalse(store.state.value is RemoteSessionUiState.Ready) + transport.initialHistoryGate!!.complete(Unit); runCurrent() + assertEquals("s-code", assertIs(store.state.value).selectedSessionId) + store.stop() + } + + @Test + fun switchingSessionsCancelsThePreviousInitialHistoryWait() = runTest { + val previousGate = CompletableDeferred() + val transport = FakeSessionTransport().apply { initialHistoryGate = previousGate } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + transport.initialHistoryGate = null + store.dispatch(RemoteSessionIntent.Open("s-cowork")); runCurrent() + assertEquals("s-cowork", assertIs(store.state.value).selectedSessionId) + previousGate.complete(Unit); runCurrent() + assertEquals("s-cowork", assertIs(store.state.value).timeline!!.sessionId) + assertEquals(1, transport.activeSubscriptions) + store.stop() + } + @Test fun additiveRevisionConstructorsKeepLegacySourceShape() { val succeeded = CreateSessionOperationState.Succeeded("request", "session", null) @@ -98,7 +157,33 @@ class RemoteSessionStoreTest { } @Test - fun initialListingAndCatalogRequestsOverlapWithoutChangingReadyOrdering() = runTest { + fun workspaceDirectorySeparatesSamePathSshLoadsAndRetry() = runTest { + val transport = FakeSessionTransport().apply { + listSessionsOverride = { command -> + """{"resp":"ok","has_more":false,"sessions":[{"id":"${command.remoteConnectionId}","title":"Branch","agent_type":"code"}]}""" + } + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.LoadWorkspaceSessions("/repo", "a", "host-a")) + store.dispatch(RemoteSessionIntent.LoadWorkspaceSessions("/repo", "b", "host-b")) + advanceUntilIdle() + val first = store.workspaceDirectory.value.workspace("/repo", "a", "host-a")!! + val second = store.workspaceDirectory.value.workspace("/repo", "b", "host-b")!! + assertEquals(listOf("a"), first.sessions.map { it.id }) + assertEquals(listOf("b"), second.sessions.map { it.id }) + assertEquals("host-a", first.sessions.single().workspaceIdentity?.remoteSshHost) + assertNull(store.workspaceDirectory.value.workspace("/repo")) + store.dispatch(RemoteSessionIntent.RetryWorkspaceSessions("/repo/", "a", "host-a")) + advanceUntilIdle() + val requests = transport.commands.filter { it.cmd == "list_sessions" } + assertEquals(listOf("a", "b", "a"), requests.map { it.remoteConnectionId }) + assertEquals(listOf("host-a", "host-b", "host-a"), requests.map { it.remoteSshHost }) + assertEquals(2, store.workspaceDirectory.value.workspaces.size) + assertEquals(second, store.workspaceDirectory.value.workspace("/repo", "b", "host-b")) + } + + @Test + fun slowCatalogDoesNotBlockAuthoritativeSessionNavigation() = runTest { val transport = FakeSessionTransport() val listGate = CompletableDeferred() val catalogGate = CompletableDeferred() @@ -120,7 +205,10 @@ class RemoteSessionStoreTest { listGate.complete(Unit) runCurrent() - assertIs(store.state.value) + val listing = assertIs(store.state.value) + assertFalse(listing.busy) + assertEquals(listOf("s-code", "s-cowork", "s-agentic"), listing.sessions.map { it.id }) + assertNull(listing.modelCatalog) catalogGate.complete(Unit) advanceUntilIdle() @@ -129,6 +217,28 @@ class RemoteSessionStoreTest { assertEquals("model-primary", ready.modelCatalog?.defaultModels?.primary) } + @Test + fun conversationOpensWhileInitialCatalogRequestIsStillPending() = runTest { + val transport = FakeSessionTransport() + val catalogGate = CompletableDeferred() + transport.commandGates["get_model_catalog"] = catalogGate + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Load) + runCurrent() + store.dispatch(RemoteSessionIntent.Open("s-code")) + runCurrent() + val opened = assertIs(store.state.value) + assertEquals("s-code", opened.selectedSessionId) + assertNotNull(opened.timeline) + assertFalse(opened.busy) + assertFalse(catalogGate.isCompleted) + store.dispatch(RemoteSessionIntent.UpdateDraft("draft after navigation")) + catalogGate.complete(Unit) + runCurrent() + assertEquals("draft after navigation", assertIs(store.state.value).draft) + store.stop() + } + @Test fun cancelledInitialCatalogCannotOverwriteANewerTargetLoad() = runTest { val transport = FakeSessionTransport() @@ -260,6 +370,52 @@ class RemoteSessionStoreTest { store.dispatch(RemoteSessionIntent.Stop) } + @Test + fun optionalSettingsReadsCoalesceAndDoNotBlockSending() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + transport.commands.clear() + val catalogGate = CompletableDeferred() + val permissionGate = CompletableDeferred() + transport.commandGates["get_model_catalog"] = catalogGate + transport.commandGates["get_permission_mode"] = permissionGate + repeat(2) { + store.dispatch(RemoteSessionIntent.RefreshModelCatalog) + store.dispatch(RemoteSessionIntent.RefreshPermissionMode) + runCurrent() + } + assertFalse(assertIs(store.state.value).busy) + assertEquals(1, transport.commands.count { it.cmd == "get_model_catalog" }) + assertEquals(1, transport.commands.count { it.cmd == "get_permission_mode" }) + store.dispatch(RemoteSessionIntent.SendMessage("s-code", "send during optional refresh")); runCurrent() + assertEquals(1, transport.commands.count { it.cmd == "send_message" }) + store.dispatch(RemoteSessionIntent.UpdateDraft("next message")) + catalogGate.complete(Unit); permissionGate.complete(Unit); runCurrent() + assertEquals("next message", assertIs(store.state.value).draft) + store.stop() + } + + @Test + fun cancelledSettingsReadCannotOverwriteAnotherConversation() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + transport.nonCancellableCommands += "get_model_catalog" + store.dispatch(RemoteSessionIntent.RefreshModelCatalog); runCurrent() + val late = transport.lateCommandContinuations.remove("get_model_catalog")!! + store.dispatch(RemoteSessionIntent.Open("s-cowork")); runCurrent() + store.dispatch(RemoteSessionIntent.UpdateDraft("other conversation")) + val before = assertIs(store.state.value) + late.resume(Unit); runCurrent() + val after = assertIs(store.state.value) + assertEquals("s-cowork", after.selectedSessionId) + assertEquals(before.modelCatalog, after.modelCatalog) + assertEquals(before.timeline, after.timeline) + assertEquals("other conversation", after.draft) + store.stop() + } + @Test fun sendingPublishesPendingAndFailedMessagesWithoutPolling() = runTest { val transport = FakeSessionTransport() @@ -339,6 +495,86 @@ class RemoteSessionStoreTest { store.dispatch(RemoteSessionIntent.Stop) } + @Test + fun hostCatalogInvalidationRefreshesSelectedSessionModel() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")) + runCurrent() + store.dispatch(RemoteSessionIntent.UpdateDraft("keep this draft")) + val notices = kotlinx.coroutines.flow.MutableSharedFlow() + store.bindCatalog(notices) + runCurrent() + transport.catalogSessionModelId = "model-from-another-controller" + val gate = CompletableDeferred() + transport.commandGates["get_model_catalog"] = gate + transport.commands.clear() + notices.emit(com.openbitfun.mobile.core.feature.relay.HostCatalogNotice.Changed) + runCurrent() + notices.emit(com.openbitfun.mobile.core.feature.relay.HostCatalogNotice.Changed) + runCurrent() + gate.complete(Unit) + runCurrent() + assertEquals(2, transport.commands.count { it.cmd == "get_model_catalog" }) + val ready = assertIs(store.state.value) + assertEquals("model-from-another-controller", ready.timeline?.selectedModelId) + assertEquals("keep this draft", ready.draft) + assertTrue(transport.commands.any { it.cmd == "get_model_catalog" && it.sessionId == "s-code" }) + store.stop() + } + + @Test + fun openingSessionLoadsItsModelWithoutBlockingReadyOrLeakingAcrossNavigation() = runTest { + val transport = FakeSessionTransport() + val gate = CompletableDeferred() + transport.commandGates["get_model_catalog"] = gate + transport.catalogSessionModelId = "old-session-model" + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")) + runCurrent() + val ready = assertIs(store.state.value) + assertFalse(ready.busy) + assertTrue(transport.commands.any { it.cmd == "get_model_catalog" && it.sessionId == "s-code" }) + store.dispatch(RemoteSessionIntent.UpdateDraft("old draft")) + store.dispatch(RemoteSessionIntent.Open("s-new")) + runCurrent() + transport.catalogSessionModelId = "new-session-model" + gate.complete(Unit) + runCurrent() + val newSession = assertIs(store.state.value) + assertEquals("s-new", newSession.selectedSessionId) + assertEquals("new-session-model", newSession.timeline?.selectedModelId) + assertFalse(newSession.busy) + store.stop() + } + + @Test + fun catalogRefreshReadsCurrentSessionSelectionWithoutLosingDraft() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")) + runCurrent() + store.dispatch(RemoteSessionIntent.SelectModel("s-code", "model-primary")) + runCurrent() + store.dispatch(RemoteSessionIntent.UpdateDraft("unsent draft")) + transport.catalogSessionModelId = "externally-selected-model" + transport.commands.clear() + store.dispatch(RemoteSessionIntent.RefreshModelCatalog) + runCurrent() + val request = transport.commands.single { it.cmd == "get_model_catalog" } + assertEquals("s-code", request.sessionId) + val ready = assertIs(store.state.value) + assertEquals("externally-selected-model", ready.timeline?.selectedModelId) + assertEquals("unsent draft", ready.draft) + assertFalse(ready.busy) + // Older peers may omit the session-specific selection. + transport.catalogSessionModelId = null + store.dispatch(RemoteSessionIntent.RefreshModelCatalog) + runCurrent() + assertEquals("externally-selected-model", assertIs(store.state.value).timeline?.selectedModelId) + store.stop() + } + @Test fun modelCatalogContractIsTheStaticSupportedFact() { assertEquals("get_model_catalog", ModelCatalogContract.commandName) @@ -865,6 +1101,32 @@ class RemoteSessionStoreTest { assertTrue(sessionTransport.commands.none { it.cmd == "create_session" }) } + @Test + fun loadOlderMessagesUsesHistoryCursorEvenWhenLatestPageHasNoVisibleMessages() = runTest { + val transport = FakeSessionTransport().apply { + initialEvents = listOf(buildJsonObject { + put("session_id", "s-code"); put("event", "session-record") + put("payload", buildJsonObject { + put("sessionId", "s-code"); put("id", "turn/deleted") + put("revision", 10); put("deleted", true) + }) + }, historyReady(true)) + olderEvents = listOf(richRecord("s-code", "old", 0, 1, "completed", "older"), historyReady(false)) + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + val initial = assertIs(store.state.value) + assertTrue(initial.hasMoreMessages) + assertTrue(initial.timeline?.persistedMessages.orEmpty().isEmpty()) + store.dispatch(RemoteSessionIntent.LoadOlderMessages); runCurrent() + assertEquals(1, transport.olderRequests) + val ready = assertIs(store.state.value) + assertEquals(listOf("old_user", "old_assistant"), ready.timeline?.persistedMessages?.map { it.id }) + assertFalse(ready.hasMoreMessages) + assertFalse(ready.busy) + store.stop() + } + @Test fun loadOlderMessagesPrependsThePreviousTranscriptPage() = runTest { val transport = FakeSessionTransport() @@ -921,6 +1183,26 @@ class RemoteSessionStoreTest { store.stop() } + @Test + fun remoteCreateUsesReturnedWorkspaceIdentityBeforeRequestedScope() = runTest { + val base = FakeSessionTransport() + val transport = object : RemoteCommandTransport { + override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { + return if (command.cmd == "create_session") RelayJson.decodeFromString(deserializer, + """{"resp":"ok","session_id":"s-new","workspace_path":"/actual","remote_connection_id":"actual-connection","remote_ssh_host":"actual-host"}""") + else base.send(deserializer, command, timeoutMs) + } + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.CreateSession("code", "", "", null, "/requested", "requested-connection", "requested-host")) + runCurrent() + val created = assertIs(store.state.value).sessions.first { it.id == "s-new" } + assertEquals("/actual", created.workspacePath) + assertEquals("actual-connection", created.workspaceIdentity?.remoteConnectionId) + assertEquals("actual-host", created.workspaceIdentity?.remoteSshHost) + store.stop() + } + @Test fun crossWorkspaceCreateDoesNotSwitchTheDesktopAndStaysProjected() = runTest { val transport = FakeSessionTransport() @@ -937,6 +1219,7 @@ class RemoteSessionStoreTest { modelId = null, workspacePath = "/other", remoteConnectionId = "saved-ssh", + remoteSshHost = "ssh.example", ), ) runCurrent() @@ -944,9 +1227,12 @@ class RemoteSessionStoreTest { assertTrue(transport.commands.none { it.cmd == "get_workspace_info" }) assertEquals("/other", transport.commands.first { it.cmd == "create_session" }.workspacePath) assertEquals("saved-ssh", transport.commands.first { it.cmd == "create_session" }.remoteConnectionId) + assertEquals("ssh.example", transport.commands.first { it.cmd == "create_session" }.remoteSshHost) val created = assertIs(store.state.value) .sessions.first { it.id == "s-new" } assertEquals("/other", created.workspacePath) + assertEquals("saved-ssh", created.workspaceIdentity?.remoteConnectionId) + assertEquals("ssh.example", created.workspaceIdentity?.remoteSshHost) store.dispatch(RemoteSessionIntent.Refresh) runCurrent() assertTrue(assertIs(store.state.value).sessions.any { it.id == "s-new" }) @@ -1376,6 +1662,25 @@ class RemoteSessionStoreTest { store.dispatch(RemoteSessionIntent.Stop) } + @Test + fun explicitRefreshAfterStreamFailureKeepsSelectedTranscriptAndDraft() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.UpdateDraft("keep this unsent draft")); runCurrent() + val before = assertIs(store.state.value) + transport.streamError?.invoke(RelayTransportException(RelayFailure.NetworkUnreachable)) + runCurrent() + assertEquals(ConnectionPhase.RECONNECTING, store.connectionPhase.value) + store.dispatch(RemoteSessionIntent.Refresh); runCurrent() + val after = assertIs(store.state.value) + assertEquals(before.selectedSessionId, after.selectedSessionId) + assertEquals(before.timeline, after.timeline) + assertEquals(before.draft, after.draft) + assertEquals(ConnectionPhase.CONNECTED, store.connectionPhase.value) + store.stop() + } + @Test fun idleHealthRecoversWithoutDiscardingListAndStopsInBackground() = runTest { val transport = FakeSessionTransport() @@ -1385,6 +1690,7 @@ class RemoteSessionStoreTest { transport.pingFailure = RelayFailure.NetworkUnreachable store.dispatch(RemoteSessionIntent.SetForeground(true)); runCurrent() assertEquals(ConnectionPhase.RECONNECTING, store.connectionPhase.value) + assertEquals(10_000L, transport.pingTimeoutMs) assertEquals(sessions, assertIs(store.state.value).sessions) transport.pingFailure = null advanceTimeBy(15_000); runCurrent() @@ -1396,6 +1702,24 @@ class RemoteSessionStoreTest { store.stop() } + @Test + fun healthDeadlineIncludesTransportPreparationAndCanRecover() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Load); advanceUntilIdle() + val gate = kotlinx.coroutines.CompletableDeferred() + transport.commandGates["ping"] = gate + store.dispatch(RemoteSessionIntent.SetForeground(true)); runCurrent() + advanceTimeBy(9_999); runCurrent() + assertEquals(ConnectionPhase.CONNECTED, store.connectionPhase.value) + advanceTimeBy(1); runCurrent() + assertEquals(ConnectionPhase.RECONNECTING, store.connectionPhase.value) + gate.complete(Unit) + advanceTimeBy(15_000); runCurrent() + assertEquals(ConnectionPhase.CONNECTED, store.connectionPhase.value) + store.stop() + } + @Test fun slowHealthProbesDoNotOverlapAndLateResultsCannotChangeStoppedState() = runTest { val transport = FakeSessionTransport() @@ -1415,14 +1739,27 @@ class RemoteSessionStoreTest { } @Test - fun openTranscriptUsesDurableStreamInsteadOfExtraPings() = runTest { + fun openTranscriptStillProbesHostHealthWithoutPollingOrDiscardingDraft() = runTest { val transport = FakeSessionTransport() val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.UpdateDraft("retain this draft")); runCurrent() + val before = assertIs(store.state.value) + transport.pingFailure = RelayFailure.NetworkUnreachable store.dispatch(RemoteSessionIntent.SetForeground(true)); runCurrent() - advanceTimeBy(30_000); runCurrent() + assertEquals(1, transport.commands.count { it.cmd == "ping" }) + assertEquals(ConnectionPhase.RECONNECTING, store.connectionPhase.value) + val disconnected = assertIs(store.state.value) + assertEquals(before.timeline, disconnected.timeline) + assertEquals(before.draft, disconnected.draft) + transport.pingFailure = null + advanceTimeBy(15_000); runCurrent() + assertEquals(ConnectionPhase.CONNECTED, store.connectionPhase.value) assertTrue(transport.commands.none { it.cmd == "poll_session" }) - assertTrue(transport.commands.none { it.cmd == "ping" }) + store.dispatch(RemoteSessionIntent.SetForeground(false)) + val count = transport.commands.count { it.cmd == "ping" } + advanceTimeBy(30_000); runCurrent() + assertEquals(count, transport.commands.count { it.cmd == "ping" }) store.stop() } @@ -1524,6 +1861,134 @@ class RemoteSessionStoreTest { store.stop() } + @Test + fun olderPageInFlightDoesNotReplaceLiveCompletionWithAnOlderRevision() = runTest { + val gate = CompletableDeferred() + val transport = FakeSessionTransport().apply { + initialEvents = listOf(richRecord("s-code", "live", 1, 2, "inprogress", "partial"), historyReady(true)) + olderGate = gate + olderEvents = listOf( + richRecord("s-code", "old", 0, 1, "completed", "older"), + richRecord("s-code", "live", 1, 1, "inprogress", "obsolete"), + historyReady(false), + ) + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.LoadOlderMessages); runCurrent() + store.dispatch(RemoteSessionIntent.LoadOlderMessages); runCurrent() + assertEquals(1, transport.olderRequests) + assertFalse(assertIs(store.state.value).busy) + store.dispatch(RemoteSessionIntent.UpdateDraft("draft while loading history")) + assertEquals(HistoryLoadState.LOADING, assertIs(store.state.value).historyLoadState) + transport.streamEvents.emit(richRecord("s-code", "live", 1, 3, "inprogress", "updated")); runCurrent() + assertEquals("updated", assertIs(store.state.value).timeline?.activeTurn?.text) + transport.streamEvents.emit(richRecord("s-code", "live", 1, 4, "completed", "final reply")); runCurrent() + gate.complete(Unit); runCurrent() + val ready = assertIs(store.state.value) + assertEquals(listOf("old_user", "old_assistant", "live_user", "live_assistant"), ready.timeline?.persistedMessages?.map { it.id }) + assertEquals("final reply", ready.timeline?.persistedMessages?.last()?.text) + assertEquals("draft while loading history", ready.draft) + assertEquals(HistoryLoadState.IDLE, ready.historyLoadState) + assertNull(ready.timeline?.activeTurn) + assertFalse(ready.hasMoreMessages) + assertFalse(ready.busy) + store.stop() + } + + @Test + fun loadingOlderMessagesDoesNotCancelInitialModelHydration() = runTest { + val catalogGate = CompletableDeferred() + val transport = FakeSessionTransport().apply { + initialEvents = listOf(richRecord("s-code", "new", 1, 1, "completed", "new"), historyReady(true)) + olderEvents = listOf(richRecord("s-code", "old", 0, 1, "completed", "old"), historyReady(false)) + commandGates["get_model_catalog"] = catalogGate + catalogSessionModelId = "hydrated-model" + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.LoadOlderMessages); runCurrent() + catalogGate.complete(Unit); runCurrent() + assertEquals("hydrated-model", assertIs(store.state.value).timeline?.selectedModelId) + store.stop() + } + + @Test + fun olderPageFailureKeepsTranscriptAndExposesRetryWithoutBlockingComposer() = runTest { + val transport = FakeSessionTransport().apply { + initialEvents = listOf(richRecord("s-code", "new", 1, 1, "completed", "new"), historyReady(true)) + olderEvents = listOf(richRecord("s-code", "old", 0, 1, "completed", "old"), historyReady(false)) + olderFailure = true + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.UpdateDraft("keep this draft")) + store.dispatch(RemoteSessionIntent.LoadOlderMessages) + assertEquals(HistoryLoadState.LOADING, assertIs(store.state.value).historyLoadState) + runCurrent() + val failed = assertIs(store.state.value) + assertEquals(HistoryLoadState.FAILED, failed.historyLoadState) + assertFalse(failed.busy) + assertTrue(failed.hasMoreMessages) + assertEquals("new", failed.timeline?.persistedMessages?.last()?.text) + assertEquals("keep this draft", failed.draft) + transport.olderFailure = false + store.dispatch(RemoteSessionIntent.LoadOlderMessages); runCurrent() + val ready = assertIs(store.state.value) + assertEquals(HistoryLoadState.IDLE, ready.historyLoadState) + assertEquals(2, transport.olderRequests) + assertEquals("keep this draft", ready.draft) + assertEquals(listOf("old_user", "old_assistant", "new_user", "new_assistant"), ready.timeline?.persistedMessages?.map { it.id }) + store.stop() + } + + @Test + fun switchingSessionsCancelsTheIndependentOlderPage() = runTest { + val gate = CompletableDeferred() + val transport = FakeSessionTransport().apply { + initialEvents = listOf(richRecord("s-code", "new", 1, 1, "completed", "new"), historyReady(true)) + olderEvents = listOf(richRecord("s-code", "old", 0, 1, "completed", "old"), historyReady(false)) + olderGate = gate + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.LoadOlderMessages); runCurrent() + transport.initialEvents = emptyList() + store.dispatch(RemoteSessionIntent.Open("s-agentic")); runCurrent() + gate.complete(Unit); runCurrent() + val ready = assertIs(store.state.value) + assertEquals("s-agentic", ready.selectedSessionId) + assertTrue(ready.timeline?.persistedMessages.orEmpty().none { it.id == "old_user" }) + assertEquals(HistoryLoadState.IDLE, ready.historyLoadState) + assertFalse(ready.busy) + store.stop() + } + + @Test + fun permissionSaveUsesHostModeInsteadOfRequestedMode() = runTest { + val transport = FakeSessionTransport().apply { + permissionSaveJson = """{"resp":"ok","mode":"auto"}""" + } + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + store.dispatch(RemoteSessionIntent.SetPermissionMode(SessionPermissionMode.FULL_ACCESS)); runCurrent() + assertEquals(SessionPermissionMode.AUTO, assertIs(store.state.value).permissionMode) + store.stop() + } + + @Test + fun legacyPermissionSaveReadsBackAuthorityAndDoesNotInventMissingMode() = runTest { + val transport = FakeSessionTransport() + val store = RemoteSessionStore(this, transport, relayStreamStore = TestRelayStreamStore()) + store.dispatch(RemoteSessionIntent.Open("s-code")); runCurrent() + transport.commands.clear() + transport.permissionModeJson = """{"resp":"ok"}""" + store.dispatch(RemoteSessionIntent.SetPermissionMode(SessionPermissionMode.FULL_ACCESS)); runCurrent() + assertEquals(listOf("set_permission_mode", "get_permission_mode"), transport.commands.map { it.cmd }) + assertEquals(SessionPermissionMode.UNKNOWN, assertIs(store.state.value).permissionMode) + store.stop() + } + @Test fun aRefusedPermissionChangeLeavesTheSessionStanding() = runTest { val transport = FakeSessionTransport() @@ -1577,11 +2042,18 @@ private class AssistantWorkspaceTransport( } private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStreamTransport { + var initialHistoryGate: CompletableDeferred? = null var initialEvents = emptyList() var olderEvents = emptyList() var olderRequests = 0 + var olderGate: CompletableDeferred? = null + var olderFailure = false var activeSubscriptions = 0 - override suspend fun loadOlder(sessionId: String) { olderRequests++; olderEvents.forEach { streamEvents.emit(it) } } + override suspend fun loadOlder(sessionId: String) { + olderRequests++; olderGate?.await() + if (olderFailure) error("History read failed") + olderEvents.forEach { streamEvents.emit(it) } + } val streamEvents = MutableSharedFlow(extraBufferCapacity = 10) var streamError: ((Throwable) -> Unit)? = null var streamCaughtUp: (() -> Unit)? = null @@ -1589,7 +2061,7 @@ private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStream override suspend fun subscribe(sessionId: String, replica: SessionStreamReplica, onError: (Throwable) -> Unit, onCaughtUp: () -> Unit): Flow = flow { streamError = onError; streamCaughtUp = onCaughtUp activeSubscriptions++ - try { initialEvents.forEach { emit(it) }; onCaughtUp(); streamEvents.collect { emit(it) } } + try { initialEvents.forEach { emit(it) }; initialHistoryGate?.await(); onCaughtUp(); streamEvents.collect { emit(it) } } finally { activeSubscriptions-- } } @@ -1601,6 +2073,7 @@ private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStream var permissionFailure: RelayFailure? = null var permissionModeJson: String? = null + var permissionSaveJson: String? = null /** When set, `list_sessions` serves one row per offset so paging is observable. */ var paged: Boolean = false @@ -1615,10 +2088,12 @@ private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStream /** When set, `get_model_catalog` fails with the selected transport result. */ var modelCatalogFailure: RelayFailure? = null + var catalogSessionModelId: String? = null /** When set, the open conversation's health poll fails below the desktop. */ var pollFailure: RelayFailure? = null var pingFailure: RelayFailure? = null + var pingTimeoutMs: Long? = null /** When set, `send_message` fails below the desktop while the draft is kept. */ var sendMessageFailure: RelayFailure? = null @@ -1666,7 +2141,10 @@ private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStream rejection?.let { throw RelayTransportException(RelayFailure.RemoteRejected(it)) } failure?.let { throw RelayTransportException(it) } } - if (command.cmd == "ping") pingFailure?.let { throw RelayTransportException(it) } + if (command.cmd == "ping") { + pingTimeoutMs = timeoutMs + pingFailure?.let { throw RelayTransportException(it) } + } if (command.cmd == "poll_session") { pollFailure?.let { throw RelayTransportException(it) } } @@ -1689,6 +2167,7 @@ private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStream "resp":"ok", "catalog":{ "version":7, + "session_model_id":${catalogSessionModelId?.let { "\"$it\"" } ?: "null"}, "models":[{ "id":"model-primary","name":"Primary","provider":"account", "base_url":"","model_name":"primary","enabled":true @@ -1707,7 +2186,8 @@ private class FakeSessionTransport : RemoteCommandTransport, RemoteSessionStream "create_session" -> """{"resp":"ok","session_id":"s-new"}""" "set_session_model" -> """{"resp":"ok","model_id":"model-primary"}""" "send_message", "steer_turn", "build_plan" -> """{"resp":"ok","turn_id":"t-1"}""" - "ping", "delete_session", "update_session_title", "answer_question", "set_permission_mode", "confirm_tool" -> + "set_permission_mode" -> permissionSaveJson ?: """{"resp":"ok"}""" + "ping", "delete_session", "update_session_title", "answer_question", "confirm_tool" -> """{"resp":"ok"}""" else -> error("Unexpected command ${command.cmd}") } diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplicaTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplicaTest.kt index 7347346204..4e987dbf25 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplicaTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/session/SessionRecordReplicaTest.kt @@ -33,6 +33,79 @@ class SessionRecordReplicaTest { replica.apply(record(5, "completed", "restored")) assertEquals("restored", replica.messages()[1].text) } + @Test fun restoringParentHeaderDoesNotResurrectChildrenBeforeDeletion() { + for (parent in listOf("turn/t", "round/r")) { + val replica = SessionRecordReplica("s") + replica.apply(record(1, "inprogress", "deleted content")) + replica.apply(buildJsonObject { + put("sessionId", "s"); put("id", parent); put("revision", 10); put("deleted", true) + }) + val source = record(11, "inprogress", "") + replica.apply(JsonObject((source - "item" - "round") + ("id" to JsonPrimitive("turn/t")))) + assertEquals("", replica.messages()[1].text) + val child = record(12, "inprogress", "new child") + val item = child.getValue("item").jsonObject + val data = item.getValue("data").jsonObject + replica.apply(JsonObject(child + mapOf( + "id" to JsonPrimitive("item/new"), + "item" to JsonObject(item + ("data" to JsonObject(data + ("id" to JsonPrimitive("new"))))) + ))) + assertEquals("new child", replica.messages()[1].text) + } + } + + @Test fun retrySupersededItemsAreNotPresented() { + for (kind in listOf("text", "thinking", "tool")) { + for (status in listOf("superseded", "retry_superseded")) { + val replica = SessionRecordReplica("s") + val source = record(1, "inprogress", "obsolete") + val item = source.getValue("item").jsonObject + val data = item.getValue("data").jsonObject + replica.apply(JsonObject(source + ("item" to JsonObject(item + mapOf( + "type" to JsonPrimitive(kind), + "data" to JsonObject(data + ("status" to JsonPrimitive(status))) + ))))) + val assistant = replica.messages()[1] + assertTrue(assistant.items.orEmpty().isEmpty(), "$kind/$status must not reach presentation") + assertEquals("", assistant.text) + assertTrue(assistant.tools.orEmpty().isEmpty()) + } + } + } + + @Test fun subagentSessionIdentityMarksItemsWithoutLegacyBoolean() { + val replica = SessionRecordReplica("s") + val source = record(1, "inprogress", "child output") + val item = source.getValue("item").jsonObject + val data = item.getValue("data").jsonObject + replica.apply(JsonObject(source + ("item" to JsonObject(item + + ("data" to JsonObject(data + ("subagentSessionId" to JsonPrimitive("child-session")))))))) + assertEquals(true, replica.messages()[1].items.orEmpty().single().isSubagent) + assertEquals("", replica.messages()[1].text, "Child output must not leak through the aggregate text fallback") + } + + @Test fun toolRecordUsesCallIdentityAndResultStatusWhenLegacyStatusIsMissing() { + for ((success, expected) in listOf(true to "completed", false to "failed")) { + val replica = SessionRecordReplica("s") + val source = record(1, "inprogress", "") + val data = buildJsonObject { + put("id", "i"); put("toolName", "Read"); put("startTime", 123) + put("toolCall", buildJsonObject { put("id", "call-id"); put("input", buildJsonObject { put("path", "/test") }) }) + put("toolResult", buildJsonObject { put("success", success); put("result", "output"); put("durationMs", 9) }) + } + replica.apply(JsonObject(source + ("item" to buildJsonObject { put("type", "tool"); put("data", data) }))) + val tool = replica.messages()[1].tools.orEmpty().single() + assertEquals("call-id", tool.id) + assertEquals(expected, tool.status) + assertEquals(123L, tool.startMs) + assertEquals(9L, tool.durationMs) + val zero = JsonObject(source + mapOf("revision" to JsonPrimitive(2), + "item" to buildJsonObject { put("type", "tool"); put("data", JsonObject(data + ("durationMs" to JsonPrimitive(0)))) })) + replica.apply(zero) + assertEquals(0L, replica.messages()[1].tools.orEmpty().single().durationMs) + } + } + @Test fun pendingApprovalIsASeparateControlOverlayAndCompletionClearsIt() { val replica = SessionRecordReplica("s") replica.apply(record(1, "inprogress", "working")) @@ -43,6 +116,33 @@ class SessionRecordReplicaTest { replica.apply(record(2, "completed", "done")) assertTrue(replica.messages()[1].tools.orEmpty().isEmpty()) } + @Test fun rejectedRoundBindingDoesNotMutateOrConsumeRevision() { + val replica = SessionRecordReplica("s") + replica.apply(record(1, "inprogress", "working")) + val before = replica.messages() + val valid = record(2, "completed", "done") + val invalid = JsonObject(valid + ("round" to JsonObject(valid.getValue("round").jsonObject + + ("turnId" to JsonPrimitive("foreign-turn"))))) + assertFails { replica.apply(invalid) } + assertEquals(before, replica.messages(), "Rejected records must not partially update the turn") + replica.apply(valid) + assertEquals("done", replica.messages()[1].text) + assertEquals("completed", replica.messages()[1].status) + } + + @Test fun rejectedItemParentDoesNotMutateOrConsumeRevision() { + val replica = SessionRecordReplica("s") + replica.apply(record(1, "inprogress", "working")) + val before = replica.messages() + val valid = record(2, "completed", "done") + val invalid = JsonObject(valid + ("round" to JsonObject(valid.getValue("round").jsonObject + + ("id" to JsonPrimitive("other-round"))))) + assertFails { replica.apply(invalid) } + assertEquals(before, replica.messages()) + replica.apply(valid) + assertEquals("done", replica.messages()[1].text) + } + @Test fun rejectsForeignSessionBinding() { val replica = SessionRecordReplica("other") assertFails { replica.apply(record(1, "running", "private")) } diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentationTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentationTest.kt index 9bf1875ecc..9ef487527c 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentationTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/shell/RemoteSidebarPresentationTest.kt @@ -13,7 +13,7 @@ import kotlin.test.assertTrue class RemoteSidebarPresentationTest { @Test - fun selectedWorkspaceComesFirstWithoutDuplicatingItsRecentEntry() { + fun selectionDoesNotReorderOrRenameHostCatalog() { val rows = RemoteSidebarPresentation.workspaces( workspaceState = workspaceReady( selected = selected("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/repo/b", "Beta"), @@ -30,22 +30,39 @@ class RemoteSidebarPresentationTest { ), ) - assertEquals(listOf("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/repo/b", "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/repo/a"), rows.map { it.path }) - assertEquals("Beta", rows.first().name) - assertTrue(rows.first().selected) - assertEquals(listOf("b-1"), rows.first().sessions.map { it.id }) - assertEquals(listOf("a-1"), rows.last().sessions.map { it.id }) + assertEquals(listOf("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/repo/a", "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/repo/b"), rows.map { it.path }) + assertEquals("Old Beta", rows.last().name) + assertTrue(rows.last().selected) + assertEquals(listOf("b-1"), rows.last().sessions.map { it.id }) + assertEquals(listOf("a-1"), rows.first().sessions.map { it.id }) } @Test - fun sessionWithoutAWorkspacePathBelongsToTheSelectedWorkspace() { + fun selectedWorkspaceAndLegacySessionsCannotCreateCatalogRows() { val rows = RemoteSidebarPresentation.workspaces( workspaceState = workspaceReady(selected("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/repo/current", "Current"), emptyList()), sessionState = sessionReady(listOf(session("legacy", null, "assistant"))), ) - assertEquals(listOf("legacy"), rows.single().sessions.map { it.id }) - assertEquals("assistant", rows.single().sessions.single().agentType) + assertTrue(rows.isEmpty()) + } + + @Test + fun sharedNativeProjectionDistinguishesHostsAndHonorsEmptyOpenedCatalog() { + val a = recent("/repo", "A").copy(remoteConnectionId = "saved", remoteSshHost = "host-a") + val b = a.copy(name = "B", remoteSshHost = "host-b") + val ready = workspaceReady(selected("/repo", "Selected").copy(remoteConnectionId = "saved", remoteSshHost = "host-b"), listOf(a, b)) + val sessions = listOf( + session("a", "/repo", "code").copy(workspaceIdentity = com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity("/repo", "saved", "host-a")), + session("b", "/repo", "code").copy(workspaceIdentity = com.openbitfun.mobile.core.domain.RemoteWorkspaceIdentity("/repo", "saved", "host-b")), + session("legacy", "/repo", "code"), + ) + val rows = RemoteSidebarPresentation.workspacesForSessions(ready, sessions) + assertEquals(listOf(false, true), rows.map { it.selected }) + assertEquals(listOf(listOf("a"), listOf("b")), rows.map { it.sessions.map { row -> row.id } }) + val empty = ready.copy(catalog = com.openbitfun.mobile.core.feature.workspace.WorkspaceCatalogUiState( + emptyList(), com.openbitfun.mobile.core.feature.workspace.WorkspaceCatalogSource.OPENED)) + assertTrue(RemoteSidebarPresentation.workspacesForSessions(empty, sessions).isEmpty()) } private fun workspaceReady( diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStoreTest.kt index 4a715cfd9c..220d4e4577 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RemoteWorkspaceStoreTest.kt @@ -25,6 +25,54 @@ import kotlin.test.assertContentEquals @OptIn(ExperimentalCoroutinesApi::class) class RemoteWorkspaceStoreTest { + @Test + fun rejectedProviderDoesNotPublishPreviousDirectoryResponse() = runTest { + val base = FakeWorkspaceTransport() + val response = CompletableDeferred() + val transport = object : RemoteCommandTransport { + override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { + if (command.command == "get_directory_children_paginated") { + withContext(NonCancellable) { response.await() } + return RelayJson.decodeFromString(deserializer, + """{"resp":"host_invoke_result","ok":true,"value":{"children":[],"hasMore":false}}""") + } + return base.send(deserializer, command, timeoutMs) + } + } + val store = RemoteWorkspaceStore.create(this, transport, StandardTestDispatcher(testScheduler)) + store.dispatch(RemoteWorkspaceIntent.Load); advanceUntilIdle() + store.dispatch(RemoteWorkspaceIntent.OpenDeviceTools("/previous", null)); runCurrent() + store.dispatch(RemoteWorkspaceIntent.OpenDeviceTools("/next", "missing-provider")); runCurrent() + assertTrue(assertIs(store.state.value).deviceTools.failed) + response.complete(Unit); advanceUntilIdle() + val ready = assertIs(store.state.value) + assertTrue(ready.files.failed) + assertEquals("", ready.files.directory) + store.stop() + } + + @Test + fun explicitLocalWorkspaceDoesNotInheritAnSshCatalogMatch() = runTest { + val base = FakeWorkspaceTransport() + val transport = object : RemoteCommandTransport { + override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { + if (command.cmd == "list_recent_workspaces") return RelayJson.decodeFromString(deserializer, + """{"resp":"ok","workspaces":[{"path":"/repo","name":"SSH","remote_connection_id":"ssh-saved","remote_ssh_host":"host"}]}""") + return base.send(deserializer, command, timeoutMs) + } + } + val store = RemoteWorkspaceStore.create(this, transport, StandardTestDispatcher(testScheduler)) + store.dispatch(RemoteWorkspaceIntent.Load); advanceUntilIdle() + store.dispatch(RemoteWorkspaceIntent.SelectWorkspace("/repo", null, null, false)); advanceUntilIdle() + val explicit = base.commands.last { it.cmd == "set_workspace" } + assertEquals(null, explicit.remoteConnectionId) + assertEquals(null, explicit.remoteSshHost) + // Existing callers retain their saved-identity inference behavior. + store.dispatch(RemoteWorkspaceIntent.SelectWorkspace("/repo")); advanceUntilIdle() + assertEquals("ssh-saved", base.commands.last { it.cmd == "set_workspace" }.remoteConnectionId) + store.stop() + } + @Test fun deviceToolsUseRuntimeHomeAndExplicitProviderWithoutWorkspaceSelection() = runTest { val base = FakeWorkspaceTransport() @@ -63,7 +111,32 @@ class RemoteWorkspaceStoreTest { store.dispatch(RemoteWorkspaceIntent.OpenDeviceTerminal("/repo", null)); advanceUntilIdle() store.dispatch(RemoteWorkspaceIntent.OpenDeviceTerminal("/repo", "saved-1")); advanceUntilIdle() assertEquals(2, terminalCount) + store.dispatch(RemoteWorkspaceIntent.OpenDeviceTools("/unified", "saved-1")); advanceUntilIdle() + var ready = assertIs(store.state.value) + assertTrue(ready.deviceTools.visible) + assertEquals(DeviceToolsPanel.FILES, ready.deviceTools.panel) + assertEquals("/unified", ready.deviceTools.path) + assertEquals("saved-1", ready.deviceTools.connectionId) + assertEquals(null, ready.terminal.sessionId) + store.dispatch(RemoteWorkspaceIntent.SelectDeviceToolsPanel(DeviceToolsPanel.TERMINAL)); advanceUntilIdle() + assertEquals(2, terminalCount, "Selecting the terminal tab must not create a PTY") + store.dispatch(RemoteWorkspaceIntent.StartDeviceToolsTerminal); advanceUntilIdle() + val terminalId = assertIs(store.state.value).terminal.sessionId + assertEquals(3, terminalCount) + store.dispatch(RemoteWorkspaceIntent.SelectDeviceToolsPanel(DeviceToolsPanel.FILES)); advanceUntilIdle() + assertEquals(terminalId, assertIs(store.state.value).terminal.sessionId) + store.dispatch(RemoteWorkspaceIntent.OpenDeviceTools("/unified", null)); advanceUntilIdle() + assertEquals(null, assertIs(store.state.value).terminal.sessionId) + store.dispatch(RemoteWorkspaceIntent.OpenDeviceTools("/unified", "saved-1")); advanceUntilIdle() + assertEquals(terminalId, assertIs(store.state.value).terminal.sessionId) + assertEquals(3, terminalCount, "Returning to a location reuses its PTY") + store.dispatch(RemoteWorkspaceIntent.CloseDeviceTools); advanceUntilIdle() + ready = assertIs(store.state.value) + assertFalse(ready.deviceTools.visible) + assertEquals(terminalId, ready.terminal.sessionId) + assertFalse(calls.any { it.cmd == "set_workspace" }) store.stop() + assertFalse(assertIs(store.state.value).deviceTools.visible) } @Test @@ -600,6 +673,59 @@ class RemoteWorkspaceStoreTest { assertIs(assertIs(store.state.value).preview) } + @Test fun workspaceDownloadRetryRetainsOriginalSshScopeWithoutAChat() = runTest { + val transport = FakeWorkspaceTransport(downloadChunks = true).apply { savedConnection = "saved-ssh" } + val store = RemoteWorkspaceStore.create(this, transport, StandardTestDispatcher(testScheduler)) + store.dispatch(RemoteWorkspaceIntent.Load); advanceUntilIdle() + store.dispatch(RemoteWorkspaceIntent.DownloadFile("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/repo/main.rs", "main.rs", "")); advanceUntilIdle() + val first = assertIs(assertIs(store.state.value).download) + store.dispatch(RemoteWorkspaceIntent.DownloadSaveFailed(first.target.path)) + // Device tools may move to another location while the export error is visible. + store.dispatch(RemoteWorkspaceIntent.OpenDeviceFiles("/other", null)); advanceUntilIdle() + transport.commands.clear() + store.dispatch(RemoteWorkspaceIntent.RetryDownload); advanceUntilIdle() + val retried = assertIs(assertIs(store.state.value).download) + assertEquals(first.target, retried.target) + val reads = transport.commands.filter { it.cmd in setOf("get_file_info", "read_file_chunk") } + assertEquals(3, reads.size) + assertTrue(reads.all { it.sessionId == null && it.workspacePath == "/repo" && it.remoteConnectionId == "saved-ssh" }) + store.dispatch(RemoteWorkspaceIntent.DownloadSaveFailed(retried.target.path)) + store.stop() + transport.commands.clear() + store.dispatch(RemoteWorkspaceIntent.RetryDownload); advanceUntilIdle() + assertTrue(transport.commands.isEmpty(), "A stopped device must not accept an old retry") + } + + @Test fun deviceLocalDownloadDoesNotInheritSelectedSshWorkspace() = runTest { + val transport = FakeWorkspaceTransport(downloadChunks = true).apply { savedConnection = "saved-ssh" } + val store = RemoteWorkspaceStore.create(this, transport, StandardTestDispatcher(testScheduler)) + store.dispatch(RemoteWorkspaceIntent.Load); advanceUntilIdle() + store.dispatch(RemoteWorkspaceIntent.OpenDeviceFiles("/repo", null)); advanceUntilIdle() + assertFalse(assertIs(store.state.value).files.failed) + transport.commands.clear() + store.dispatch(RemoteWorkspaceIntent.DownloadFile("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/repo/main.rs", "main.rs", "")); advanceUntilIdle() + val download = assertIs(assertIs(store.state.value).download) + val reads = transport.commands.filter { it.cmd in setOf("get_file_info", "read_file_chunk") } + assertEquals(3, reads.size) + assertTrue(reads.all { it.sessionId == null && it.workspacePath == "/repo" && it.remoteConnectionId == null }) + store.dispatch(RemoteWorkspaceIntent.DownloadSaved(download.target.path)); store.stop() + } + + @Test fun deviceSshDownloadDoesNotInheritSelectedLocalWorkspace() = runTest { + val transport = FakeWorkspaceTransport(downloadChunks = true) + val store = RemoteWorkspaceStore.create(this, transport, StandardTestDispatcher(testScheduler)) + store.dispatch(RemoteWorkspaceIntent.Load); advanceUntilIdle() + store.dispatch(RemoteWorkspaceIntent.OpenDeviceFiles("/", "saved-1")); advanceUntilIdle() + assertFalse(assertIs(store.state.value).files.failed) + transport.commands.clear() + store.dispatch(RemoteWorkspaceIntent.DownloadFile("/tmp/main.rs", "main.rs", "")); advanceUntilIdle() + val download = assertIs(assertIs(store.state.value).download) + val reads = transport.commands.filter { it.cmd in setOf("get_file_info", "read_file_chunk") } + assertEquals(3, reads.size) + assertTrue(reads.all { it.sessionId == null && it.workspacePath == "/" && it.remoteConnectionId == "saved-1" }) + store.dispatch(RemoteWorkspaceIntent.DownloadSaved(download.target.path)); store.stop() + } + @Test fun workspaceDownloadCapturesSavedConnectionForEveryChunk() = runTest { val transport = FakeWorkspaceTransport(downloadChunks = true).apply { savedConnection = "saved-ssh" } val store = RemoteWorkspaceStore.create(this, transport, StandardTestDispatcher(testScheduler)) @@ -800,7 +926,9 @@ private class FakeWorkspaceTransport( commands += command commandGates[command.cmd]?.await() val json = when (command.cmd) { - "host_invoke" -> """{"resp":"host_invoke_result","ok":true,"value":[{"id":"saved-1","name":"Saved host","host":"host"}]}""" + "host_invoke" -> if (command.command == "get_directory_children_paginated") { + """{"resp":"host_invoke_result","ok":true,"value":{"children":[],"hasMore":false}}""" + } else """{"resp":"host_invoke_result","ok":true,"value":[{"id":"saved-1","name":"Saved host","host":"host"}]}""" "list_recent_workspaces" -> """{"resp":"ok","workspaces":[{"path":"/repo","name":"Repo","last_opened":"2026-08-09"}]}""" "list_assistants" -> diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStoreTest.kt index bcbb226c8d..72bab81433 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeFilesStoreTest.kt @@ -8,10 +8,43 @@ import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.json.* import kotlin.test.* +@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class RuntimeFilesStoreTest { + @Test fun lateReadCannotReplaceFilesFromAnotherSshWorkspace() = runTest { + val host = FileHost(); val store = RuntimeFilesStore(this, host) + store.browse("/repo", "/repo", "ssh-a", false); advanceUntilIdle() + var pending: kotlin.coroutines.Continuation? = null + host.beforeReply = { command -> + if (command.command == "read_file_content") kotlin.coroutines.suspendCoroutine { pending = it } + } + store.read("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/repo/file"); advanceUntilIdle() + store.browse("/repo", "/repo", "ssh-b", false); advanceUntilIdle() + val replacement = store.state.value + pending!!.resumeWith(Result.success(Unit)); advanceUntilIdle() + assertEquals(replacement, store.state.value) + assertNull(store.state.value.file) + val request = host.commands.last().args!!.jsonObject.getValue("request").jsonObject + assertEquals("ssh-b", request.getValue("remoteConnectionId").jsonPrimitive.content) + } + + @Test fun cancelledUploadBeforeDispatchClosesItsSourceWithoutSending() = runTest { + val host = FileHost(); val store = RuntimeFilesStore(this, host) + store.browse("/repo", "/repo", "saved", false); advanceUntilIdle() + var closes = 0 + store.upload("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/repo/new", object : RuntimeUploadSource { + override val size = 0L + override fun read(offset: Long, length: Int): ByteArray = error("Cancelled source must not be read") + override fun close() { closes++ } + }) + store.reset(); advanceUntilIdle() + assertEquals(1, closes) + assertTrue(host.commands.none { it.command == "workspace_file_upload" }) + assertFalse(store.state.value.busy) + } + @Test fun binaryUploadUsesBoundedReadsAndRecoversLostAcknowledgement() = runTest { val bytes = ByteArray(7 * 1024 * 1024 + 13) { 37 } - var begins = 0; var maxRead = 0; var closed = 0; var offset = 0L; var lost = false; var completed = false + var begins = 0; var finishes = 0; var maxRead = 0; var closed = 0; var offset = 0L; var lost = false; var completed = false val copied = mutableListOf() val host = object : RemoteCommandTransport { override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { @@ -26,7 +59,7 @@ class RuntimeFilesStoreTest { copied.addAll(chunk.toList()); offset += chunk.size if (!lost) { lost = true; error("ACK lost") } } - "finish" -> completed = true + "finish" -> { finishes++; completed = true; error("Finish ACK lost") } } """{"transferId":${request.getValue("transferId")},"totalBytes":${bytes.size},"nextOffset":$offset,"completed":$completed}""" } @@ -42,10 +75,40 @@ class RuntimeFilesStoreTest { }) // File adapter reads run on Dispatchers.Default, outside the virtual clock. while (store.state.value.busy) { kotlinx.coroutines.delay(1); testScheduler.runCurrent() } - assertEquals(1, begins); assertFalse(store.state.value.failed); assertTrue(completed); assertEquals(1, closed) + assertEquals(1, begins); assertEquals(1, finishes); assertFalse(store.state.value.failed); assertTrue(completed); assertEquals(1, closed) assertTrue(maxRead <= 3 * 1024 * 1024); assertContentEquals(bytes, copied.toByteArray()) } + @Test fun completedUploadWithShortFinalCursorIsNotSuccess() = runTest { + var closed = 0 + var listings = 0 + val host = object : RemoteCommandTransport { + override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { + val request = command.args!!.jsonObject.getValue("request").jsonObject + val value = if (command.command == "get_directory_children_paginated") { + listings++ + """{"children":[],"hasMore":false}""" + } else { + val action = request.getValue("action").jsonPrimitive.content + val offset = if (action == "append") 1 else 0 + """{"transferId":${request.getValue("transferId")},"totalBytes":1,"nextOffset":$offset,"completed":${action == "finish"}}""" + } + return RelayJson.decodeFromString(deserializer, """{"resp":"host_invoke_result","ok":true,"value":$value}""") + } + } + val store = RuntimeFilesStore(this, host) + store.browse("/repo", "/repo", null, false); advanceUntilIdle() + store.upload("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/repo/new", object : RuntimeUploadSource { + override val size = 1L + override fun read(offset: Long, length: Int) = byteArrayOf(42) + override fun close() { closed++ } + }) + while (store.state.value.busy) { kotlinx.coroutines.delay(1); testScheduler.runCurrent() } + assertTrue(store.state.value.failed) + assertEquals(1, listings) + assertEquals(1, closed) + } + @Test fun fileWritesUseRuntimeScopeAndOriginalHashAndFailuresPreserveContent() = runTest { val host = FileHost(); val store = RuntimeFilesStore(this, host) store.browse("/repo", "/repo", "saved-ssh", false); advanceUntilIdle() @@ -64,6 +127,34 @@ class RuntimeFilesStoreTest { assertEquals("", host.commands.last().args!!.jsonObject.getValue("request").jsonObject.getValue("expectedHash").jsonPrimitive.content) store.reset(); store.save("stale"); advanceUntilIdle(); assertTrue(store.state.value.failed) } + @Test fun hostFailureDetailSurvivesWithoutChangingRevisionAndLegacyFailureUsesFallback() = runTest { + val host = FileHost(); val store = RuntimeFilesStore(this, host) + store.browse("/repo", "/repo", "saved-ssh", false); advanceUntilIdle() + store.read("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/repo/file"); advanceUntilIdle() + host.accepted = false + host.errorDetail = "FILE_CONFLICT: File changed after it was read; refresh before saving" + store.save("unsaved draft"); advanceUntilIdle() + assertEquals(host.errorDetail, store.state.value.errorDetail) + assertTrue(store.state.value.saveConflict) + assertTrue(store.state.value.failed) + assertEquals("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/repo/file", store.state.value.file) + assertEquals("original", store.state.value.content) + host.errorDetail = null // Old hosts may omit the optional error field entirely. + store.save("retry draft") + assertNull(store.state.value.errorDetail) + assertFalse(store.state.value.saveConflict) + advanceUntilIdle() + assertTrue(store.state.value.failed) + assertNull(store.state.value.errorDetail) + val write = host.commands.last().args!!.jsonObject.getValue("request").jsonObject + assertEquals(ContentHash.sha256("original"), write.getValue("expectedHash").jsonPrimitive.content) + host.accepted = true + store.save("saved"); advanceUntilIdle() + assertFalse(store.state.value.failed) + assertNull(store.state.value.errorDetail) + assertEquals("saved", store.state.value.content) + } + @Test fun sortingResetsServerPageAndClosingEditorPreservesDirectory() = runTest { val host = FileHost(); val store = RuntimeFilesStore(this, host) store.browse("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/repo/sub", "/repo", "saved-ssh", false); advanceUntilIdle() @@ -80,17 +171,78 @@ class RuntimeFilesStoreTest { assertNull(store.state.value.file); assertEquals("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/repo/sub", store.state.value.directory) assertEquals(RuntimeFileSort.MODIFIED_ASC, store.state.value.sort) } + @Test fun listActionsUseSelectedSshAndRefreshSortedDirectoryWithoutOpeningEditor() = runTest { + val host = FileHost(); val store = RuntimeFilesStore(this, host) + host.children = """[{"path":"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/repo/folder","name":"folder","isDirectory":true}]""" + store.browse("/repo", "/repo", "saved-ssh", false, RuntimeFileSort.MODIFIED_DESC); advanceUntilIdle() + store.renameEntry("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/repo/folder", "renamed"); advanceUntilIdle() + val rename = host.commands.first { it.command == "rename_file" }.args!!.jsonObject.getValue("request").jsonObject + assertEquals("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/repo/folder", rename.getValue("oldPath").jsonPrimitive.content) + assertEquals("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/repo/renamed", rename.getValue("newPath").jsonPrimitive.content) + store.deleteEntry("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/repo/folder"); advanceUntilIdle() + val delete = host.commands.first { it.command == "delete_directory" }.args!!.jsonObject.getValue("request").jsonObject + assertFalse(delete.getValue("recursive").jsonPrimitive.boolean) + store.createEntry("new.txt", false); advanceUntilIdle() + val create = host.commands.first { it.command == "write_file_content" }.args!!.jsonObject.getValue("request").jsonObject + assertEquals("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/repo/new.txt", create.getValue("filePath").jsonPrimitive.content) + assertEquals("", create.getValue("expectedHash").jsonPrimitive.content) + assertNull(store.state.value.file) + assertEquals(RuntimeFileSort.MODIFIED_DESC, store.state.value.sort) + assertEquals(4, host.commands.count { it.command == "get_directory_children_paginated" }) + for (command in host.commands) assertEquals("saved-ssh", command.args!!.jsonObject.getValue("request").jsonObject.getValue("remoteConnectionId").jsonPrimitive.content) + } + + @Test fun failedListMutationPreservesEntriesAndStaleRowsDoNotSendDeletes() = runTest { + val host = FileHost(); val store = RuntimeFilesStore(this, host) + host.children = """[{"path":"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/repo/file","name":"file","isDirectory":false}]""" + store.browse("/repo", "/repo", null, false); advanceUntilIdle() + val entries = store.state.value.entries + host.accepted = false + store.deleteEntry("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/repo/file"); advanceUntilIdle() + assertTrue(store.state.value.failed) + assertEquals(entries, store.state.value.entries) + val count = host.commands.size + store.deleteEntry("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/repo/missing"); advanceUntilIdle() + assertEquals(count, host.commands.size) + assertTrue(store.state.value.failed) + } + + @Test fun lateMutationDoesNotRefreshReplacementProvider() = runTest { + val host = FileHost(); val store = RuntimeFilesStore(this, host) + store.browse("/repo", "/repo", "ssh-a", false); advanceUntilIdle() + var pending: kotlin.coroutines.Continuation? = null + host.beforeReply = { command -> + if (command.command == "create_directory") kotlin.coroutines.suspendCoroutine { pending = it } + } + store.createEntry("folder", true); advanceUntilIdle() + store.browse("/repo", "/repo", "ssh-b", false); advanceUntilIdle() + val replacement = store.state.value + val count = host.commands.size + pending!!.resumeWith(Result.success(Unit)); advanceUntilIdle() + assertEquals(count, host.commands.size) + assertEquals(replacement, store.state.value) + } + private class FileHost : RemoteCommandTransport { var accepted = true + var errorDetail: String? = null + var children = "[]" + var beforeReply: (suspend (RemoteCommand) -> Unit)? = null val commands = mutableListOf() override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { commands += command + beforeReply?.invoke(command) val value = when(command.command) { - "get_directory_children_paginated" -> """{"children":[],"hasMore":false}""" + "get_directory_children_paginated" -> """{"children":$children,"hasMore":false}""" "read_file_content" -> "\"original\"" else -> "null" } - return RelayJson.decodeFromString(deserializer, """{"resp":"host_invoke_result","ok":$accepted,"value":$value}""") + val response = buildJsonObject { + put("resp", "host_invoke_result"); put("ok", accepted) + put("value", RelayJson.parseToJsonElement(value)) + errorDetail?.let { put("error", it) } + } + return RelayJson.decodeFromJsonElement(deserializer, response) } } } diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStoreTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStoreTest.kt index fd86730928..a333519904 100644 --- a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStoreTest.kt +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/RuntimeTerminalStoreTest.kt @@ -10,9 +10,55 @@ import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.json.* import kotlin.test.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class RuntimeTerminalStoreTest { + @Test fun unsupportedHostReasonRemainsVisibleWithoutCreatingTerminal() = runTest { + val host = FakeTerminalHost(false).apply { failureDetail = "Unsupported host operation: terminal_create" } + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("/workspace", null); advanceUntilIdle() + assertTrue(store.state.value.failed) + assertEquals(host.failureDetail, store.state.value.errorDetail) + assertNull(store.state.value.sessionId) + assertFalse(store.state.value.busy) + store.stop() + assertNull(store.state.value.errorDetail) + } + + @Test fun recoveredEmptyHistoryClearsConnectionFailureWithoutChangingOutput() = runTest { + val host = FakeTerminalHost().apply { live = true } + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("/workspace", null); advanceUntilIdle() + val before = store.state.value + host.errors.single()(IllegalStateException("Disconnected")) + assertTrue(store.state.value.failed) + host.events.emit(buildJsonObject { put("event", "relay://session-resumed") }) + advanceUntilIdle() + assertFalse(store.state.value.failed) + assertEquals(before.output, store.state.value.output) + assertEquals(before.revision, store.state.value.revision) + host.refuseWrite = true + store.write("pwd\r"); advanceUntilIdle() + assertTrue(store.state.value.failed) + host.events.emit(buildJsonObject { put("event", "terminal-output") }) + advanceUntilIdle() + assertTrue(store.state.value.failed) + store.stop() + } + + @Test fun nonAdvancingHistoryStopsWithoutDuplicatingOutput() = runTest { + val host = FakeTerminalHost().apply { stalledHistory = true } + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("/workspace", null); advanceUntilIdle() + assertEquals(listOf(0L, 5L), host.offsets) + assertEquals("hello", store.state.value.output) + assertTrue(store.state.value.failed) + store.stop() + } + @Test fun hostOwnsTerminalAndPushNotificationReadsOnlyNewOutput() = runTest { val host = FakeTerminalHost() val store = RuntimeTerminalStore(this, host, MemoryReplica()) @@ -52,6 +98,37 @@ class RuntimeTerminalStoreTest { assertEquals(1, host.commands.count { it.command == "terminal_write" }) } + @Test fun callbacksFromStoppedStreamCannotFailReplacementTerminal() = runTest { + val host = FakeTerminalHost() + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("/first", null); advanceUntilIdle() + val staleError = host.errors.single() + store.stop() + store.open("/second", "ssh"); advanceUntilIdle() + val replacement = store.state.value + staleError(IllegalStateException("Old transport closed")) + advanceUntilIdle() + assertEquals(replacement, store.state.value) + host.errors.last()(IllegalStateException("Current transport closed")) + assertTrue(store.state.value.failed) + store.stop() + } + + @Test fun lateCloseCannotStopReplacementTerminal() = runTest { + val host = FakeTerminalHost() + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("/first", null); advanceUntilIdle() + host.holdClose = true + store.close(); advanceUntilIdle() + store.stop() + store.open("/second", "ssh"); advanceUntilIdle() + val replacement = store.state.value + host.closeContinuation!!.resume(Unit) + advanceUntilIdle() + assertEquals(replacement, store.state.value) + store.stop() + } + @Test fun hostRefusalDoesNotReportAnOpenTerminal() = runTest { val host = FakeTerminalHost(false) val store = RuntimeTerminalStore(this, host, MemoryReplica()) @@ -61,30 +138,77 @@ class RuntimeTerminalStoreTest { assertTrue(store.state.value.failed) store.stop() } + + @Test fun refusedClosePreservesTerminalAndAllowsRetry() = runTest { + val host = FakeTerminalHost() + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("/workspace", null); advanceUntilIdle() + val original = store.state.value + host.refuseClose = true + store.close(); advanceUntilIdle() + assertEquals(original.sessionId, store.state.value.sessionId) + assertEquals(original.output, store.state.value.output) + assertTrue(store.state.value.failed) + assertFalse(store.state.value.busy) + host.refuseClose = false + store.close(); advanceUntilIdle() + assertNull(store.state.value.sessionId) + assertFalse(store.state.value.failed) + store.stop() + } + + @Test fun reopeningUsesOriginalSshLocationAndDisconnectForgetsIt() = runTest { + val host = FakeTerminalHost() + val store = RuntimeTerminalStore(this, host, MemoryReplica()) + store.open("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/ssh/project", "saved-ssh"); advanceUntilIdle() + store.close(); advanceUntilIdle() + store.reopen(); advanceUntilIdle() + val creations = host.commands.filter { it.command == "terminal_create" } + assertEquals(2, creations.size) + assertEquals(creations.first().args, creations.last().args) + store.stop() + store.reopen(); advanceUntilIdle() + assertEquals(2, host.commands.count { it.command == "terminal_create" }) + assertNull(store.state.value.sessionId) + } } private class FakeTerminalHost(private val accepted: Boolean = true) : RemoteCommandTransport, RemoteSessionStreamTransport { val commands = mutableListOf() val offsets = mutableListOf() var stream = "" + var failureDetail = "" + var live = false + val events = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 4) + var holdClose = false + var stalledHistory = false + var refuseClose = false + var refuseWrite = false + var closeContinuation: Continuation? = null + val errors = mutableListOf<(Throwable) -> Unit>() override val streamIdentity = "test-account-target" override suspend fun subscribe(sessionId: String, replica: SessionStreamReplica, onError: (Throwable) -> Unit, onCaughtUp: () -> Unit): Flow { stream = sessionId - return flow { onCaughtUp(); emit(buildJsonObject { put("event", "terminal-output") }) } + errors += onError + return flow { onCaughtUp(); emit(buildJsonObject { put("event", "terminal-output") }); if (live) events.collect { emit(it) } } } override suspend fun send(deserializer: DeserializationStrategy, command: RemoteCommand, timeoutMs: Long): T { commands += command + if (command.command == "terminal_close" && holdClose) suspendCoroutine { closeContinuation = it } val value = when (command.command) { "terminal_create" -> """{"id":"term1"}""" "terminal_get_history" -> { val offset = command.args!!.jsonObject.getValue("afterOffset").jsonPrimitive.long offsets += offset - if (offset == 0L) """{"data":"hello","nextOffset":5,"cursor":11,"truncated":false}""" + if (offset == 11L) """{"data":"","nextOffset":11,"cursor":11,"truncated":false}""" + else if (offset == 0L) """{"data":"hello","nextOffset":5,"cursor":11,"truncated":false}""" + else if (stalledHistory) """{"data":"duplicate","nextOffset":5,"cursor":11,"truncated":false}""" else """{"data":" world","nextOffset":11,"cursor":11,"truncated":false}""" } else -> "null" } - return RelayJson.decodeFromString(deserializer, """{"resp":"host_invoke_result","ok":$accepted,"value":$value}""") + val ok = accepted && !(refuseClose && command.command == "terminal_close") && !(refuseWrite && command.command == "terminal_write") + return RelayJson.decodeFromString(deserializer, """{"resp":"host_invoke_result","ok":$ok,"value":$value,"error":${JsonPrimitive(failureDetail)}}""") } } private class MemoryReplica : RelayStreamStore { diff --git a/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogTest.kt b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogTest.kt new file mode 100644 index 0000000000..5eeaddf870 --- /dev/null +++ b/src/apps/mobile/shared/core-feature/src/commonTest/kotlin/com/openbitfun/mobile/core/feature/workspace/WorkspaceCatalogTest.kt @@ -0,0 +1,52 @@ +package com.openbitfun.mobile.core.feature.workspace + +import com.openbitfun.mobile.core.domain.WorkspaceAssistant +import com.openbitfun.mobile.core.protocol.RecentWorkspaceListResponse +import com.openbitfun.mobile.core.protocol.RelayJson +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class WorkspaceCatalogTest { + private val assistants = listOf(WorkspaceAssistant("/assistant", "My assistant", "a")) + + @Test + fun authoritativeEmptyDoesNotLeakHistoryOrAssistantsAndSurvivesRoundTrip() { + val response = decode("""{"workspaces":[{"path":"/closed"}],"opened_workspaces":[]}""") + val roundTrip = decode(RelayJson.encodeToString(RecentWorkspaceListResponse.serializer(), response)) + val catalog = roundTrip.sidebarCatalog(assistants) + assertEquals(WorkspaceCatalogSource.OPENED, catalog.source) + assertTrue(catalog.workspaces.isEmpty()) + assertEquals("/closed", roundTrip.workspaces.single().path) + assertEquals("/closed", catalog.recentWorkspaces.single().path) + } + + @Test + fun oldHostPayloadSelectsExplicitRecentFallback() { + for (wire in listOf("""{"workspaces":[{"path":"/history"}]}""", + """{"workspaces":[{"path":"/history"}],"opened_workspaces":null}""")) { + val response = decode(wire) + assertNull(response.openedWorkspaces) + val catalog = response.sidebarCatalog(assistants) + assertEquals(WorkspaceCatalogSource.RECENT, catalog.source) + assertEquals(listOf("/assistant", "/history"), catalog.workspaces.map { it.path }) + } + } + + @Test + fun openedMembershipAndHostIdentityWinWhileLocalAssistantNamesAreEnriched() { + val response = decode("""{"workspaces":[{"path":"/closed"}],"opened_workspaces":[ + {"path":"/assistant","name":"folder"}, + {"path":"/assistant","name":"SSH","remote_connection_id":"ssh-a","remote_ssh_host":"host"}, + {"path":"/assistant","name":"SSH B","remote_connection_id":"ssh-b","remote_ssh_host":"host"}, + {"path":"/assistant","name":"duplicate"}, + {"path":""}] }""") + val catalog = response.sidebarCatalog(assistants) + assertEquals(listOf("My assistant", "SSH", "SSH B"), catalog.workspaces.map { it.name }) + assertEquals("assistant", catalog.workspaces.first().kind) + assertEquals(listOf(null, "ssh-a", "ssh-b"), catalog.workspaces.map { it.remoteConnectionId }) + } + + private fun decode(wire: String): RecentWorkspaceListResponse = RelayJson.decodeFromString(wire) +} diff --git a/src/apps/mobile/shared/core-persistence/src/commonMain/kotlin/com/openbitfun/mobile/core/persistence/ChatLocalStore.kt b/src/apps/mobile/shared/core-persistence/src/commonMain/kotlin/com/openbitfun/mobile/core/persistence/ChatLocalStore.kt index e372c7829c..9fee409e3e 100644 --- a/src/apps/mobile/shared/core-persistence/src/commonMain/kotlin/com/openbitfun/mobile/core/persistence/ChatLocalStore.kt +++ b/src/apps/mobile/shared/core-persistence/src/commonMain/kotlin/com/openbitfun/mobile/core/persistence/ChatLocalStore.kt @@ -139,6 +139,13 @@ public class SqlDelightChatLocalStore public constructor( ) } +@Serializable +public data class PersistedWorkspaceIdentity( + public val path: String = "", + public val remoteConnectionId: String? = null, + public val remoteSshHost: String? = null, +) + @Serializable public data class PersistedRemoteSession public constructor( public val sessionId: String = "", @@ -153,6 +160,8 @@ public data class PersistedRemoteSession public constructor( public val workspaceName: String? = null, /** True until a later server list observes this confirmed-created session id. */ public val pendingConfirmed: Boolean = false, + /** Absent on legacy records; absence must not be interpreted as local ownership. */ + public val workspaceIdentity: PersistedWorkspaceIdentity? = null, ) @Serializable diff --git a/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/SessionDtos.kt b/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/SessionDtos.kt index c0f6916568..b72b75deba 100644 --- a/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/SessionDtos.kt +++ b/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/SessionDtos.kt @@ -114,6 +114,7 @@ public data class CreateSessionResponse( @SerialName("title") val title: String? = null, @SerialName("workspace_path") val workspacePath: String? = null, @SerialName("remote_connection_id") val remoteConnectionId: String? = null, + @SerialName("remote_ssh_host") val remoteSshHost: String? = null, ) : CommandStatus { /** `session_id` and `id` are the same field under two spellings. */ public val resolvedSessionId: String? get() = sessionId ?: id diff --git a/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/WorkspaceDtos.kt b/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/WorkspaceDtos.kt index bd1538f82e..8170dc5507 100644 --- a/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/WorkspaceDtos.kt +++ b/src/apps/mobile/shared/core-protocol/src/commonMain/kotlin/com/openbitfun/mobile/core/protocol/WorkspaceDtos.kt @@ -82,6 +82,8 @@ public data class RecentWorkspaceListResponse( @SerialName("resp") override val resp: String? = null, @SerialName("message") override val message: String? = null, @SerialName("workspaces") val workspaces: List = emptyList(), + /** Null means legacy host; an empty list is an authoritative empty catalog. */ + @SerialName("opened_workspaces") val openedWorkspaces: List? = null, ) : CommandStatus @Serializable diff --git a/src/apps/mobile/shared/core-transport/build.gradle.kts b/src/apps/mobile/shared/core-transport/build.gradle.kts index 143fdba7a7..051f09adba 100644 --- a/src/apps/mobile/shared/core-transport/build.gradle.kts +++ b/src/apps/mobile/shared/core-transport/build.gradle.kts @@ -64,6 +64,9 @@ kotlin { jvmMain.dependencies { implementation(libs.ktor.client.java) } + jvmTest.dependencies { + implementation(libs.ktor.client.okhttp) + } iosMain.dependencies { implementation(libs.ktor.client.darwin) } diff --git a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/AccountRealtime.kt b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/AccountRealtime.kt index fe1290fbdd..a801dd0152 100644 --- a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/AccountRealtime.kt +++ b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/AccountRealtime.kt @@ -17,6 +17,8 @@ import org.hildan.socketio.EngineIOPacket import org.hildan.socketio.SocketIOPacket import kotlin.random.Random +internal const val MAX_ACCOUNT_FRAME_BYTES = 256 * 1024 + internal interface AccountRpcConnection { val notifications: Flow get() = emptyFlow() val connections: Flow get() = emptyFlow() @@ -30,6 +32,7 @@ internal class AccountRealtime( private val http: HttpClient, private val relayUrl: String, private val token: String, + private val log: TransportLog = TransportLog.None, ) : AccountRpcConnection { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val state = MutableStateFlow(null) @@ -39,7 +42,11 @@ internal class AccountRealtime( private val connectionFailure = MutableStateFlow(null) override val notifications: SharedFlow = updates.asSharedFlow() override val connections: StateFlow = generation.asStateFlow() - private val owner = scope.launch { supervise() } + private val owner = scope.launch { supervise() }.also { job -> + job.invokeOnCompletion { cause -> + log.info("account realtime owner ended cancelled=${job.isCancelled} cause=${cause?.let { it::class.simpleName } ?: "none"}") + } + } private class Epoch(val socket: DefaultClientWebSocketSession) { val ready = CompletableDeferred() @@ -107,13 +114,16 @@ internal class AccountRealtime( val socket = withTimeout(RELAY_CONNECT_TIMEOUT_MS) { http.webSocketSession("$url/v1/updates/?EIO=4&transport=websocket") } + log.info("account realtime socket opened") epoch = Epoch(socket) state.value = epoch var idleMs = RELAY_CONNECT_TIMEOUT_MS var engineOpened = false while (currentCoroutineContext().isActive) { val frame = withTimeout(idleMs) { socket.incoming.receive() } - if (frame !is Frame.Text) throw CloudAccountException(CloudAccountFailure.MALFORMED_RESPONSE) + if (frame !is Frame.Text || frame.data.size > MAX_ACCOUNT_FRAME_BYTES) { + throw CloudAccountException(CloudAccountFailure.MALFORMED_RESPONSE) + } when (val packet = EngineIO.decodeSocketIO(frame.readText())) { is EngineIOPacket.Open -> { check(!engineOpened) @@ -140,6 +150,7 @@ internal class AccountRealtime( epoch.ready.complete(Unit) delayMs = 1000 generation.value += 1 + log.info("account realtime authenticated generation=${generation.value}") } if (name == "update" || name == "ephemeral") { val value = message.payload.getOrNull(1)?.jsonObject @@ -157,12 +168,18 @@ internal class AccountRealtime( } } } catch (cancelled: CancellationException) { - if (cancelled !is TimeoutCancellationException) throw cancelled + if (cancelled !is TimeoutCancellationException) { + log.warn("account realtime receive cancelled ownerActive=${currentCoroutineContext().isActive} cause=${cancelled::class.simpleName}") + throw cancelled + } + log.warn("account realtime timed out") } catch (failure: CloudAccountException) { connectionFailure.value = failure + log.warn("account realtime failed type=${failure::class.simpleName}") if (failure.failure == CloudAccountFailure.AUTHENTICATION) return } catch (failure: Exception) { connectionFailure.value = failure + log.warn("account realtime failed type=${failure::class.simpleName}") // Read/connect failures reconnect. Submitted calls fail in fail(). } finally { val old = epoch @@ -180,5 +197,8 @@ internal class AccountRealtime( } } - override fun close() { scope.cancel() } + override fun close() { + log.info("account realtime close requested") + scope.cancel() + } } diff --git a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClient.kt b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClient.kt index 58d9f08044..5d888138a5 100644 --- a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClient.kt +++ b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClient.kt @@ -1,5 +1,6 @@ package com.openbitfun.mobile.core.transport +import kotlinx.coroutines.delay import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.filter @@ -138,7 +139,7 @@ public class CloudAccountClient internal constructor( private val client: HttpClient, private val log: TransportLog = TransportLog.None, legacyMobileDeviceNames: Set = emptySet(), - private val realtimeFactory: (HttpClient, String, String) -> AccountRpcConnection = ::AccountRealtime, + private val realtimeFactory: (HttpClient, String, String) -> AccountRpcConnection = { client, url, token -> AccountRealtime(client, url, token, log) }, ) { private class Connection(val url: String, val token: String, val socket: AccountRpcConnection) private val realtime = MutableStateFlow(null) @@ -252,8 +253,25 @@ public class CloudAccountClient internal constructor( replica: SessionStreamReplica, onError: (Throwable) -> Unit, onCaughtUp: () -> Unit, ): kotlinx.coroutines.flow.Flow { val socket = connection(relayUrl, session.token) - val grant = deviceRpc(relayUrl, session, targetDeviceId, - RemoteCommand(cmd = "get_session_key", sessionId = sessionId), SessionKeyGrant.serializer(), RELAY_DEFAULT_TIMEOUT_MS) + var grant: SessionKeyGrant + var retryMs = 1000L + while (true) { + check(realtime.value?.socket === socket) { "Account changed" } + try { + grant = deviceRpc(relayUrl, session, targetDeviceId, + RemoteCommand(cmd = "get_session_key", sessionId = sessionId), SessionKeyGrant.serializer(), RELAY_DEFAULT_TIMEOUT_MS) + break + } catch (cancelled: CancellationException) { + throw cancelled + } catch (error: Throwable) { + log.warn("session stream grant failed session=$sessionId type=${error::class.simpleName} failure=${(error as? CloudAccountException)?.failure}") + if (!isRetryableStreamFailure(error)) throw error + // Key retrieval is read-only. Do not extend this policy to user mutations. + onError(error) + delay(retryMs) + retryMs = (retryMs * 2).coerceAtMost(30000) + } + } check(grant.sessionId == sessionId) { "Session key grant binding mismatch" } val historyKey = targetDeviceId + ":" + sessionId val historyRequests = Channel>(Channel.RENDEZVOUS) @@ -267,7 +285,11 @@ public class CloudAccountClient internal constructor( check(realtime.value?.socket === socket) { "Account changed" } requestWithoutBody(relayUrl, "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/v3/sessions/" + encodePathSegment(grant.relaySessionId) + "/messages?before_seq=" + before + "&limit=100", HttpMethod.Get, StreamPage.serializer(), session.token, RELAY_DEFAULT_TIMEOUT_MS) - }, olderRequests = historyRequests, onError = onError, onCaughtUp = onCaughtUp, prefetchOlder = sessionId != "@host/catalog").onCompletion { + }, olderRequests = historyRequests, onError = { error -> + log.warn("session stream read failed session=$sessionId type=${error::class.simpleName} failure=${(error as? CloudAccountException)?.failure}") + onError(error) + }, onCaughtUp = onCaughtUp, prefetchOlder = sessionId != "@host/catalog").onCompletion { cause -> + log.info("session stream ended session=$sessionId cause=${cause?.let { it::class.simpleName } ?: "none"}") if (historyReaders[historyKey] === historyRequests) historyReaders.remove(historyKey) historyRequests.close() } diff --git a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/RelayHttpClient.kt b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/RelayHttpClient.kt index 44133e4ee6..c9b9a59fd9 100644 --- a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/RelayHttpClient.kt +++ b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/RelayHttpClient.kt @@ -45,7 +45,9 @@ public fun relayHttpClient(): HttpClient = HttpClient { configureForRelay() } private fun HttpClientConfig<*>.configureForRelay() { expectSuccess = false - install(io.ktor.client.plugins.websocket.WebSockets) { maxFrameSize = 256 * 1024 } + // OkHttp rejects session.maxFrameSize assignment during the handshake. + // Enforce the protocol limit in AccountRealtime before decoding instead. + install(io.ktor.client.plugins.websocket.WebSockets) install(HttpTimeout) { connectTimeoutMillis = RELAY_CONNECT_TIMEOUT_MS requestTimeoutMillis = RELAY_DEFAULT_TIMEOUT_MS diff --git a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/SessionStream.kt b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/SessionStream.kt index 7e7f86da60..535e43512d 100644 --- a/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/SessionStream.kt +++ b/src/apps/mobile/shared/core-transport/src/commonMain/kotlin/com/openbitfun/mobile/core/transport/SessionStream.kt @@ -92,7 +92,23 @@ internal fun sessionStream( replica.commitHistory(messages.map { it.content.c }, maxOf(replica.cursor(), messages.lastOrNull()?.seq ?: 0), if (more) boundary else -boundary) emit(buildJsonObject { put("session_id", sessionId); put("event", "relay://session-ready"); put("payload", buildJsonObject { put("hasMore", more); put("oldestSeq", boundary); put("cursor", replica.cursor()) }) }) } - if (replica.cursor() == 0L) older(9007199254740991L) + if (replica.cursor() == 0L) { + var initialRetry = 1000L + while (true) { + // The host grants the key from its durable local log before the + // publisher necessarily creates the relay session. A first-read + // 404 is pending publication, never an empty transcript. + try { older(9007199254740991L); break } + catch (cancelled: CancellationException) { throw cancelled } + catch (failure: Throwable) { + onError(failure) + if (!isRetryableStreamFailure(failure) && + !(failure is CloudAccountException && failure.statusCode == 404)) throw failure + delay(initialRetry) + initialRetry = (initialRetry * 2).coerceAtMost(30000) + } + } + } else emit(buildJsonObject { put("session_id", sessionId); put("event", "relay://session-ready"); put("payload", buildJsonObject { put("hasMore", replica.historyBefore() > 0); put("oldestSeq", kotlin.math.abs(replica.historyBefore())); put("cursor", replica.cursor()) }) }) if (prefetchOlder) launch { while (replica.historyBefore() > 0) { @@ -160,7 +176,7 @@ internal fun sessionStream( } catch (cancelled: CancellationException) { throw cancelled } catch (failure: Throwable) { onError(failure) - if (failure !is CloudAccountException || failure.failure !in setOf(CloudAccountFailure.NETWORK, CloudAccountFailure.TIMEOUT, CloudAccountFailure.RELAY_UNAVAILABLE, CloudAccountFailure.RATE_LIMITED)) throw failure + if (!isRetryableStreamFailure(failure)) throw failure // Retrying reads cannot execute host mutations. Caller cancellation remains immediate. delay(retry); retry = (retry * 2).coerceAtMost(30000); wake.trySend(null) } @@ -191,3 +207,9 @@ private suspend fun decryptFragments(sessionId: String, key: String, parts: List check(event["session_id"]?.jsonPrimitive?.content == sessionId && event["event"] is JsonPrimitive && event.containsKey("payload")) { "Session encryption binding mismatch" } return event } + +internal fun isRetryableStreamFailure(failure: Throwable): Boolean = + failure is CloudAccountException && failure.failure in setOf( + CloudAccountFailure.NETWORK, CloudAccountFailure.TIMEOUT, + CloudAccountFailure.RELAY_UNAVAILABLE, CloudAccountFailure.RATE_LIMITED, + ) diff --git a/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClientTest.kt b/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClientTest.kt index cdaff4f9b2..80c9cb386a 100644 --- a/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClientTest.kt +++ b/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/CloudAccountClientTest.kt @@ -14,6 +14,10 @@ import io.ktor.client.request.HttpRequestData import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.http.headersOf +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.encodeToJsonElement @@ -196,6 +200,73 @@ class CloudAccountClientTest { assertEquals(CloudAccountFailure.RELAY_UNAVAILABLE, (error.cause as CloudAccountException).failure) } + @Test + fun sessionGrantRetriesTransientFailureButStopsAtAuthenticationFailure() = runTest { + var calls = 0 + val failures = mutableListOf() + val client = rpcClient(DeviceIdentity.publicKey(ByteArray(32) { 11 })) { _, _ -> + calls++ + throw CloudAccountException(if (calls == 1) CloudAccountFailure.TIMEOUT else CloudAccountFailure.AUTHENTICATION) + } + val error = assertFailsWith { + client.subscribeSession("http://192.168.1.2:9700", CloudAccountSession("token-1", "user-1", ByteArray(32)), + "desktop-1", "session", EmptyReplica(), { failures += it }, {}) + } + assertEquals(CloudAccountFailure.AUTHENTICATION, error.failure) + assertEquals(2, calls) + assertEquals(listOf(CloudAccountFailure.TIMEOUT), failures.map { (it as CloudAccountException).failure }) + } + + @Test + fun cancellingSessionGrantBackoffPreventsAnotherRequest() = runTest { + var calls = 0 + val entered = CompletableDeferred() + val client = rpcClient(DeviceIdentity.publicKey(ByteArray(32) { 11 })) { _, _ -> + calls++ + entered.complete(Unit) + throw CloudAccountException(CloudAccountFailure.NETWORK) + } + val job = launch { + client.subscribeSession("http://192.168.1.2:9700", CloudAccountSession("token-1", "user-1", ByteArray(32)), + "desktop-1", "session", EmptyReplica(), {}, {}) + } + entered.await() + assertEquals(1, calls) + job.cancelAndJoin() + advanceUntilIdle() + assertEquals(1, calls) + } + + @Test + fun sessionGrantRecoversAndOnlyRepeatsTheReadCommand() = runTest { + var calls = 0 + val master = ByteArray(32) + val peerSecret = ByteArray(32) { 11 } + val key = DeviceIdentity.messageKey(peerSecret, DeviceIdentity.publicKey(master)) + val client = rpcClient(DeviceIdentity.publicKey(peerSecret)) { _, params -> + val envelope = RelayJson.decodeFromJsonElement(EncryptedPayload.serializer(), params) + val plain = CloudAccountCipher.decrypt(Base64.Default.decode(envelope.encryptedData), key, + Base64.Default.decode(envelope.nonce)).decodeToString() + assertEquals("get_session_key", RelayJson.decodeFromString(RemoteCommand.serializer(), plain).cmd) + if (++calls == 1) throw CloudAccountException(CloudAccountFailure.TIMEOUT) + val nonce = ByteArray(12) { 20 } + val response = """{"resp":"session_key","session_id":"session","relay_session_id":"opaque","key":"${Base64.Default.encode(ByteArray(32))}"}""" + val encrypted = CloudAccountCipher.encrypt(response.encodeToByteArray(), key, nonce) + RelayJson.encodeToJsonElement(EncryptedPayload.serializer(), + EncryptedPayload(Base64.Default.encode(encrypted), Base64.Default.encode(nonce))) + } + client.subscribeSession("http://192.168.1.2:9700", CloudAccountSession("token-1", "user-1", master), + "desktop-1", "session", EmptyReplica(), {}, {}) + assertEquals(2, calls) + } + + private class EmptyReplica : SessionStreamReplica { + override suspend fun eventIds() = emptyList() + override suspend fun cursor() = 0L + override suspend fun fragments(eventId: String) = emptyList() + override suspend fun commit(fragments: List, cursor: Long) {} + } + private class FakeRpc(private val reply: suspend (String, JsonElement) -> JsonElement) : AccountRpcConnection { override suspend fun call(target: String, params: JsonElement, timeoutMs: Long): JsonElement = reply(target, params) override fun close() {} diff --git a/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/SessionStreamTest.kt b/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/SessionStreamTest.kt index 516d0fcd0e..71710aa6e0 100644 --- a/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/SessionStreamTest.kt +++ b/src/apps/mobile/shared/core-transport/src/commonTest/kotlin/com/openbitfun/mobile/core/transport/SessionStreamTest.kt @@ -8,7 +8,92 @@ import kotlinx.coroutines.test.* import kotlinx.serialization.json.* import kotlin.test.* +@OptIn(ExperimentalCoroutinesApi::class) class SessionStreamTest { + @Test fun initialHistoryTimeoutRecoversWithoutResubscribingOrAdvancingCursor() = runTest { + var reads = 0; var caughtUp = 0 + val failures = mutableListOf() + val replica = MemoryReplica() + val owner = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + sessionStream("session", "unused", "relay", emptyFlow(), emptyFlow(), replica, + readPage = { StreamPage(emptyList(), false) }, + readBefore = { reads++; if (reads == 1) throw CloudAccountException(CloudAccountFailure.TIMEOUT); StreamPage(emptyList(), false) }, + olderRequests = Channel(), onError = { failures.add(it) }, onCaughtUp = { caughtUp++ }, + prefetchOlder = false, + ).collect() + } + runCurrent() + assertEquals(1, failures.size) + assertEquals(0, caughtUp) + assertEquals(0L, replica.cursor()) + advanceTimeBy(1000); runCurrent() + assertEquals(2, reads) + assertEquals(1, caughtUp) + assertTrue(owner.isActive) + owner.cancel() + } + + @Test fun initialHistoryWaitsForHostPublicationWithoutCommittingEmptyHistory() = runTest { + var reads = 0 + var caughtUp = 0 + val replica = MemoryReplica() + val owner = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + sessionStream("session", "unused", "relay", emptyFlow(), emptyFlow(), replica, + readPage = { StreamPage(emptyList(), false) }, + readBefore = { + reads++ + if (reads < 3) throw CloudAccountException(CloudAccountFailure.MALFORMED_RESPONSE, 404) + StreamPage(emptyList(), false) + }, olderRequests = Channel(), onError = {}, onCaughtUp = { caughtUp++ }, prefetchOlder = false, + ).collect() + } + runCurrent() + advanceTimeBy(1000); runCurrent() + assertEquals(2, reads) + assertEquals(0, caughtUp) + assertEquals(0L, replica.historyBefore()) + advanceTimeBy(2000); runCurrent() + assertEquals(3, reads) + assertEquals(1, caughtUp) + assertTrue(owner.isActive) + owner.cancel() + } + + @Test fun stoppingDuringInitialBackoffCancelsFurtherReads() = runTest { + var reads = 0 + val owner = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + sessionStream("session", "unused", "relay", emptyFlow(), emptyFlow(), MemoryReplica(), + readPage = { error("Must not read forward history") }, + readBefore = { reads++; throw CloudAccountException(CloudAccountFailure.TIMEOUT) }, + olderRequests = Channel(), onError = {}, onCaughtUp = {}, prefetchOlder = false, + ).collect() + } + runCurrent(); assertEquals(1, reads) + owner.cancelAndJoin() + advanceTimeBy(60_000); runCurrent() + assertEquals(1, reads) + } + + @Test fun authenticationAndInvalidHistoryFailWithoutRetryOrCursorMutation() = runTest { + for (failure in listOf(CloudAccountException(CloudAccountFailure.AUTHENTICATION), + CloudAccountException(CloudAccountFailure.MALFORMED_RESPONSE), IllegalStateException("Invalid sequence"))) { + var reads = 0; var reported = 0 + val replica = MemoryReplica() + val result = runCatching { + sessionStream("session", "unused", "relay", emptyFlow(), emptyFlow(), replica, + readPage = { error("Must not read forward history") }, + readBefore = { reads++; throw failure }, olderRequests = Channel(), + onError = { reported++ }, onCaughtUp = { error("Must not report connected") }, prefetchOlder = false, + ).collect() + } + val actual = assertNotNull(result.exceptionOrNull()) + assertEquals(failure::class, actual::class) + assertEquals(failure.message, actual.message) + assertEquals(1, reads); assertEquals(1, reported) + assertEquals(0L, replica.cursor()) + } + } + @Test fun latestPageRepairsFragmentBoundaryAndOlderHistoryKeepsForwardCursor() = runTest { val fragments = RelayJson.decodeFromString>(FRAGMENTS) val messages = (0..1).flatMap { event -> fragments.mapIndexed { index, fragment -> diff --git a/src/apps/mobile/shared/core-transport/src/jvmTest/kotlin/com/openbitfun/mobile/core/transport/OkHttpRealtimeTest.kt b/src/apps/mobile/shared/core-transport/src/jvmTest/kotlin/com/openbitfun/mobile/core/transport/OkHttpRealtimeTest.kt new file mode 100644 index 0000000000..463c5de17e --- /dev/null +++ b/src/apps/mobile/shared/core-transport/src/jvmTest/kotlin/com/openbitfun/mobile/core/transport/OkHttpRealtimeTest.kt @@ -0,0 +1,101 @@ +package com.openbitfun.mobile.core.transport + +import io.ktor.client.engine.okhttp.OkHttp +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.first +import kotlinx.serialization.json.* +import java.net.ServerSocket +import java.security.MessageDigest +import java.util.Base64 +import kotlin.test.Test +import kotlin.test.assertEquals + +/** Uses Android's actual engine: the JVM production engine does not reject frame-limit setters. */ +class OkHttpRealtimeTest { + @Test + fun androidEngineAuthenticatesAndReceivesRpcAcknowledgement() = exerciseReconnect(false) + + @Test + fun androidEngineReconnectsAfterSilentSocketTimeout() = exerciseReconnect(true) + + @Test + fun androidEngineReconnectsAfterConnectionReset() = exerciseReconnect(true, true) + + private fun exerciseReconnect(interruptFirst: Boolean, resetFirst: Boolean = false): Unit = runBlocking { + ServerSocket(0).use { server -> + server.soTimeout = 10_000 + val received = CompletableDeferred() + val host = async(Dispatchers.IO) { + repeat(if (interruptFirst) 2 else 1) { connectionIndex -> + server.accept().use { socket -> + socket.soTimeout = 10_000 + val input = socket.getInputStream() + val output = socket.getOutputStream() + val header = StringBuilder() + while (!header.endsWith("\r\n\r\n")) { + val byte = input.read() + check(byte >= 0) + header.append(byte.toChar()) + } + val key = header.lines().first { it.startsWith("Sec-WebSocket-Key:", true) } + .substringAfter(':').trim() + val accept = Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1") + .digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").toByteArray())) + output.write(("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\n" + + "Connection: Upgrade\r\nSec-WebSocket-Accept: $accept\r\n\r\n").toByteArray()) + fun send(text: String) { + val bytes = text.toByteArray() + check(bytes.size < 126) + output.write(byteArrayOf(0x81.toByte(), bytes.size.toByte())) + output.write(bytes) + output.flush() + } + fun receive(): String { + val opcode = input.read() + check(opcode == 0x81) { "Expected text frame, received opcode $opcode" } + val lengthByte = input.read() + check(lengthByte and 0x80 != 0) + var length = lengthByte and 0x7f + if (length == 126) length = (input.read() shl 8) or input.read() + check(length < 65536) + val mask = input.readNBytes(4) + val bytes = input.readNBytes(length) + return ByteArray(length) { (bytes[it].toInt() xor mask[it % 4].toInt()).toByte() } + .toString(Charsets.UTF_8) + } + send("0{\"sid\":\"test\",\"upgrades\":[],\"pingInterval\":${if (interruptFirst && connectionIndex == 0) 100 else 25000},\"pingTimeout\":${if (interruptFirst && connectionIndex == 0) 100 else 20000}}") + check(receive().startsWith("40")) + send("40{\"sid\":\"test\"}") + send("42[\"auth-ok\",{}]") + if (interruptFirst && connectionIndex == 0) { + // Stop sending Engine.IO heartbeats without closing TCP. + if (resetFirst) socket.setSoLinger(true, 0) else delay(500) + return@use + } + val request = receive() + check(request.startsWith("42")) + val ackId = request.substring(2, request.indexOf('[')) + send("43$ackId[{\"ok\":true,\"result\":{\"value\":42}}]") + // Keep the epoch alive until the caller receives the ack. + received.await() + } + } + } + val http = relayHttpClient(OkHttp.create()) + val realtime = AccountRealtime(http, "http://127.0.0.1:${server.localPort}", "test-token") + try { + if (interruptFirst) withTimeout(10_000) { realtime.connections.first { it >= 2 } } + val result = withTimeout(10_000) { + realtime.call("host", buildJsonObject { put("cmd", "ping") }, 5000) + } + assertEquals(42, result.jsonObject.getValue("value").jsonPrimitive.int) + assertEquals(if (interruptFirst) 2 else 1, realtime.connections.value) + } finally { + received.complete(Unit) + realtime.close() + http.close() + host.cancelAndJoin() + } + } + } +} diff --git a/src/crates/assembly/core/AGENTS.md b/src/crates/assembly/core/AGENTS.md index 1356147688..b1b2231546 100644 --- a/src/crates/assembly/core/AGENTS.md +++ b/src/crates/assembly/core/AGENTS.md @@ -226,6 +226,9 @@ Agent-profile canonicalization in the focused configuration suite: ```bash cargo test -p openbitfun-core --no-default-features --features remote-connect --lib service::config:: cargo test -p openbitfun-core --no-default-features --features remote-connect --lib service::remote_connect::settings_sync::tests +cargo test --locked -p openbitfun-core --no-default-features --features remote-connect --lib service::remote_connect::permission_publication::tests +cargo test --locked -p openbitfun-core --no-default-features --features remote-connect --lib service_agent_runtime::tests::local_workspace_marker_is_not_remote_routing_authority +cargo test --locked -p openbitfun-core --no-default-features --features remote-connect --lib service_agent_runtime::tests::remote_workspace_catalog_tracks_opened_rows_and_assistant_identity ``` Focused workspace-IO and snapshot regression entry points (use the matching diff --git a/src/crates/assembly/core/src/service/filesystem/path_operations.rs b/src/crates/assembly/core/src/service/filesystem/path_operations.rs index b14696d1e5..a4bf1c4022 100644 --- a/src/crates/assembly/core/src/service/filesystem/path_operations.rs +++ b/src/crates/assembly/core/src/service/filesystem/path_operations.rs @@ -6,6 +6,38 @@ use crate::service::remote_ssh::{ get_remote_workspace_manager, lookup_remote_connection_with_hint, RemoteFileService, }; +/// Resolve an explicit file-operation connection without registering a workspace. +/// Existing workspace providers remain supported; saved SSH profiles also own +/// device-level paths outside opened workspaces. +pub async fn resolve_explicit_path_connection(path: &str, id: &str) -> Result { + if !path.starts_with('/') || path.contains('\0') { + return Err("Remote file operation requires an absolute POSIX path".into()); + } + if let Some(entry) = + crate::service::remote_ssh::workspace_state::lookup_remote_connection_scoped(path, id).await + { + return Ok(entry.connection_id); + } + #[cfg(feature = "ssh-remote")] + { + let state = + crate::service::remote_ssh::workspace_state::ensure_saved_connection_services().await?; + let ssh = state + .get_ssh_manager() + .await + .ok_or("SSH manager is unavailable")?; + if ssh + .get_saved_connections() + .await + .iter() + .any(|profile| profile.id == id) + { + return Ok(id.to_string()); + } + } + Err("Remote connection is unavailable or not saved on this runtime; local fallback was not attempted".into()) +} + async fn remote( path: &str, hint: Option<&str>, @@ -14,17 +46,11 @@ async fn remote( return Ok(None); } let hint = hint.map(str::trim).filter(|value| !value.is_empty()); - let entry = if let Some(id) = hint { - crate::service::remote_ssh::workspace_state::lookup_remote_connection_scoped(path, id).await + let connection_id = if let Some(id) = hint { + resolve_explicit_path_connection(path, id).await? + } else if let Some(entry) = lookup_remote_connection_with_hint(path, None).await { + entry.connection_id } else { - lookup_remote_connection_with_hint(path, None).await - }; - let Some(entry) = entry else { - if hint.is_some() { - return Err( - "Remote workspace is unavailable or does not own the requested path".into(), - ); - } return Ok(None); }; let manager = @@ -33,7 +59,7 @@ async fn remote( .get_file_service() .await .ok_or("Remote file service is unavailable")?; - Ok(Some((service, entry.connection_id))) + Ok(Some((service, connection_id))) } pub async fn read_text( @@ -256,6 +282,36 @@ pub async fn exists( #[cfg(test)] mod tests { + #[tokio::test] + async fn explicit_file_scope_retains_registered_provider_and_rejects_invalid_paths() { + let manager = crate::service::remote_ssh::workspace_state::init_remote_workspace_manager(); + let root = format!("/routing-test-{}", uuid::Uuid::new_v4()); + manager + .register_remote_workspace( + root.clone(), + "file-scope-test".into(), + "Fixture".into(), + "host".into(), + ) + .await; + assert_eq!( + super::resolve_explicit_path_connection(&format!("{root}/file"), "file-scope-test") + .await + .unwrap(), + "file-scope-test" + ); + for path in ["relative/file", "C:\\file", "/invalid\0file"] { + assert!( + super::resolve_explicit_path_connection(path, "file-scope-test") + .await + .is_err() + ); + } + manager + .unregister_remote_workspace("file-scope-test", &root) + .await; + } + #[tokio::test] async fn explicit_local_scope_bypasses_same_path_remote_routing() { let temp = tempfile::tempdir().unwrap(); diff --git a/src/crates/assembly/core/src/service/remote_connect/mod.rs b/src/crates/assembly/core/src/service/remote_connect/mod.rs index 405dfc358e..6556250648 100644 --- a/src/crates/assembly/core/src/service/remote_connect/mod.rs +++ b/src/crates/assembly/core/src/service/remote_connect/mod.rs @@ -12,6 +12,7 @@ pub mod account_runtime; pub mod bot; pub mod embedded_relay_host; pub mod lan; +mod permission_publication; pub mod remote_server; pub mod device { @@ -1108,6 +1109,7 @@ pub async fn synchronize_session_record_turn( /// Bridge the runtime-owned question mailbox to the account log. Only revisions /// travel here; controllers read the authoritative live mailbox on invalidation. pub fn start_session_interaction_publication(publisher: &Arc) { + permission_publication::start(publisher); #[cfg(feature = "workspace-runtime")] start_host_catalog_publication(publisher); let mut closed = publisher.subscribe_closed(); diff --git a/src/crates/assembly/core/src/service/remote_connect/permission_publication.rs b/src/crates/assembly/core/src/service/remote_connect/permission_publication.rs new file mode 100644 index 0000000000..781fd84873 --- /dev/null +++ b/src/crates/assembly/core/src/service/remote_connect/permission_publication.rs @@ -0,0 +1,104 @@ +//! Bridge runtime permission authority into the existing controller mailbox invalidation. +use std::collections::BTreeSet; +use std::sync::Arc; + +use super::session_log::SessionPublisher; + +pub(super) fn start(publisher: &Arc) { + let manager = match crate::product_runtime::core_permission_request_manager() { + Ok(manager) => manager, + Err(error) => { + log::error!("Unable to subscribe to runtime permission changes: {error}"); + return; + } + }; + // Subscribe before taking the initial snapshot so startup cannot miss a request. + let mut changes = manager.subscribe(); + let mut closed = publisher.subscribe_closed(); + let weak = Arc::downgrade(publisher); + tokio::spawn(async move { + let mut previous = BTreeSet::new(); + loop { + if *closed.borrow() { + break; + } + let snapshot = manager.interactive_pending_snapshot(); + let current = snapshot + .requests + .iter() + .map(|request| request.session_id.clone()) + .collect(); + let sessions = affected_sessions(&previous, ¤t); + let Some(publisher) = weak.upgrade() else { + break; + }; + let events = sessions.into_iter().map(|session_id| { + (session_id.clone(), "session-interaction-changed".to_string(), + serde_json::json!({"sessionId": session_id, "permissionsRevision": snapshot.revision})) + }).collect::>(); + if !events.is_empty() { + tokio::select! { + biased; + _ = closed.changed() => break, + result = publisher.append_batch(events) => if let Err(error) = result { + log::error!("Unable to persist permission mailbox invalidation: {error}"); + } + } + } + drop(publisher); + previous = current; + tokio::select! { + biased; + _ = closed.changed() => break, + event = changes.recv() => match event { + Ok(_) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}, + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + } + }); +} + +// Removed requests must invalidate their old session too. Rebuilding from the +// live snapshot on every notification also recovers from a lagged broadcast; +// no request payloads or credentials are copied into the durable session log. +fn affected_sessions(previous: &BTreeSet, current: &BTreeSet) -> BTreeSet { + previous + .union(current) + .filter(|session| !session.is_empty()) + .cloned() + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sessions(ids: &[&str]) -> BTreeSet { + ids.iter().map(|id| (*id).to_string()).collect() + } + + #[test] + fn initial_pending_requests_invalidate_their_sessions() { + assert_eq!( + affected_sessions(&sessions(&[]), &sessions(&["a", "b"])), + sessions(&["a", "b"]) + ); + } + + #[test] + fn reply_or_cancellation_invalidates_the_now_empty_mailbox() { + assert_eq!( + affected_sessions(&sessions(&["a"]), &sessions(&[])), + sessions(&["a"]) + ); + } + + #[test] + fn lag_recovery_invalidates_removed_and_current_sessions_without_empty_routes() { + assert_eq!( + affected_sessions(&sessions(&["a", "b"]), &sessions(&["b", "c", ""])), + sessions(&["a", "b", "c"]) + ); + } +} diff --git a/src/crates/assembly/core/src/service_agent_runtime.rs b/src/crates/assembly/core/src/service_agent_runtime.rs index 025d6a6da9..03d218939a 100644 --- a/src/crates/assembly/core/src/service_agent_runtime.rs +++ b/src/crates/assembly/core/src/service_agent_runtime.rs @@ -420,6 +420,20 @@ fn workspace_metadata_string( .map(ToOwned::to_owned) } +#[cfg(feature = "remote-connect")] +fn remote_workspace_metadata( + kind: &crate::service::workspace::WorkspaceKind, + metadata: &std::collections::HashMap, + key: &str, +) -> Option { + // Local persistence uses sshHost=localhost as an identity marker. It is + // not an SSH routing hint; real SSH connections to localhost remain remote. + if *kind != crate::service::workspace::WorkspaceKind::Remote { + return None; + } + workspace_metadata_string(metadata, key) +} + #[cfg(feature = "remote-connect")] pub(crate) fn remote_workspace_display_name( workspace: &crate::service::workspace::WorkspaceInfo, @@ -449,9 +463,17 @@ pub(crate) async fn remote_opened_workspace_catalog( name: remote_workspace_display_name(&workspace).to_string(), path: workspace.root_path.to_string_lossy().to_string(), last_opened: workspace.last_accessed.to_rfc3339(), - kind: remote_workspace_kind(workspace.workspace_kind), - remote_connection_id: workspace_metadata_string(&workspace.metadata, "connectionId"), - remote_ssh_host: workspace_metadata_string(&workspace.metadata, "sshHost"), + kind: remote_workspace_kind(workspace.workspace_kind.clone()), + remote_connection_id: remote_workspace_metadata( + &workspace.workspace_kind, + &workspace.metadata, + "connectionId", + ), + remote_ssh_host: remote_workspace_metadata( + &workspace.workspace_kind, + &workspace.metadata, + "sshHost", + ), }) .collect() } @@ -468,13 +490,18 @@ async fn current_remote_workspace_facts() -> Option { path: root_path.to_string_lossy().to_string(), name: workspace.name, git_branch: git_branch_for_workspace_path(&root_path), - kind: remote_workspace_kind(workspace.workspace_kind), + kind: remote_workspace_kind(workspace.workspace_kind.clone()), assistant_id: workspace.assistant_id, - remote_connection_id: workspace_metadata_string( + remote_connection_id: remote_workspace_metadata( + &workspace.workspace_kind, &workspace.metadata, "connectionId", ), - remote_ssh_host: workspace_metadata_string(&workspace.metadata, "sshHost"), + remote_ssh_host: remote_workspace_metadata( + &workspace.workspace_kind, + &workspace.metadata, + "sshHost", + ), } }) } @@ -501,13 +528,8 @@ async fn open_workspace_with_snapshot( .await .map_err(|error| error.to_string())?; let remote_connection_id = info.remote_ssh_connection_id().map(str::to_string); - let remote_ssh_host = info - .metadata - .get("sshHost") - .and_then(|value| value.as_str()) - .map(str::trim) - .filter(|value| !value.is_empty()) - .map(str::to_string); + let remote_ssh_host = + remote_workspace_metadata(&info.workspace_kind, &info.metadata, "sshHost"); Ok(RemoteWorkspaceUpdate { path: info.root_path.to_string_lossy().to_string(), name: info.name, @@ -2919,12 +2941,17 @@ impl RemoteWorkspaceRuntimeHost for CoreRemoteWorkspaceRuntimeHost { path: workspace.root_path.to_string_lossy().to_string(), name: workspace.name.clone(), last_opened: workspace.last_accessed.to_rfc3339(), - kind: remote_workspace_kind(workspace.workspace_kind), - remote_connection_id: workspace_metadata_string( + kind: remote_workspace_kind(workspace.workspace_kind.clone()), + remote_connection_id: remote_workspace_metadata( + &workspace.workspace_kind, &workspace.metadata, "connectionId", ), - remote_ssh_host: workspace_metadata_string(&workspace.metadata, "sshHost"), + remote_ssh_host: remote_workspace_metadata( + &workspace.workspace_kind, + &workspace.metadata, + "sshHost", + ), }) .collect() } @@ -3346,6 +3373,33 @@ mod tests { }; use crate::OpenBitFunError; + #[test] + fn local_workspace_marker_is_not_remote_routing_authority() { + use crate::service::workspace::WorkspaceKind; + let metadata = std::collections::HashMap::from([ + ("sshHost".to_string(), serde_json::json!("localhost")), + ( + "connectionId".to_string(), + serde_json::json!("saved-localhost-ssh"), + ), + ]); + for kind in [WorkspaceKind::Normal, WorkspaceKind::Assistant] { + assert_eq!(remote_workspace_metadata(&kind, &metadata, "sshHost"), None); + assert_eq!( + remote_workspace_metadata(&kind, &metadata, "connectionId"), + None + ); + } + assert_eq!( + remote_workspace_metadata(&WorkspaceKind::Remote, &metadata, "sshHost"), + Some("localhost".into()) + ); + assert_eq!( + remote_workspace_metadata(&WorkspaceKind::Remote, &metadata, "connectionId"), + Some("saved-localhost-ssh".into()) + ); + } + #[tokio::test] async fn missing_output_session_cannot_borrow_the_selected_workspace() { let missing = "missing-output-session-route-test"; @@ -3400,6 +3454,9 @@ mod tests { let opened = remote_opened_workspace_catalog(&service).await; assert_eq!(opened.len(), 2); + assert!(opened + .iter() + .all(|row| row.remote_connection_id.is_none() && row.remote_ssh_host.is_none())); let assistant_row = opened .iter() .find(|row| row.path == assistant.root_path.to_string_lossy()) diff --git a/src/mobile-web/tests/native-terminal-renderer-browser.test.mjs b/src/mobile-web/tests/native-terminal-renderer-browser.test.mjs index c0f2b945fe..a4d082b44b 100644 --- a/src/mobile-web/tests/native-terminal-renderer-browser.test.mjs +++ b/src/mobile-web/tests/native-terminal-renderer-browser.test.mjs @@ -8,7 +8,7 @@ test('bundled native terminal bridge renders ordered ANSI frames, rejects gaps a const server=createServer(async(req,res)=>{try{const name=req.url==='/'?'index.html':req.url.slice(1);if(!['index.html','terminal.js','terminal.css'].includes(name)){res.writeHead(404).end();return;}res.setHeader('Content-Type',name.endsWith('.js')?'application/javascript':name.endsWith('.css')?'text/css':'text/html');res.end(await readFile(new URL(name,root)));}catch{res.writeHead(500).end();}}); await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));const browser=await launchBrowser(); try{ - const page=await browser.newPage();await page.evaluateOnNewDocument(()=>{window.events=[];window.OpenBitFunTerminalHost={postMessage:text=>window.events.push(JSON.parse(text))};}); + const page=await browser.newPage();await page.setViewport({width:390,height:844,isMobile:true,hasTouch:true});await page.evaluateOnNewDocument(()=>{window.events=[];window.OpenBitFunTerminalHost={postMessage:text=>window.events.push(JSON.parse(text))};}); await page.goto(`http://127.0.0.1:${server.address().port}/`); await page.waitForFunction(()=>window.events.some(e=>e.type==='ready')); await page.evaluate(()=>{ @@ -22,7 +22,20 @@ test('bundled native terminal bridge renders ordered ANSI frames, rejects gaps a }); await page.waitForFunction(()=>document.querySelector('.xterm-rows')?.textContent.trim()==='GREEN'); assert.equal(await page.evaluate(()=>window.events.filter(e=>e.type==='resync').length),1); - await page.click('.xterm-helper-textarea');await page.keyboard.type('pwd');await page.keyboard.press('Enter'); + // Theme changes are presentation updates: they must neither consume a + // stream revision nor reset the terminal buffer. + await page.evaluate(()=>window.OpenBitFunTerminal.setTheme({background:'#ffffff',foreground:'#171717'})); + await page.waitForFunction(()=>getComputedStyle(document.querySelector('.xterm')).backgroundColor==='rgb(255, 255, 255)'); + await page.evaluate(()=>window.OpenBitFunTerminal.setTheme({background:'#151514',foreground:'#f4f3ef'})); + await page.waitForFunction(()=>getComputedStyle(document.querySelector('.xterm')).backgroundColor==='rgb(21, 21, 20)'); + assert.equal(await page.$eval('.xterm-rows',element=>element.textContent.trim()),'GREEN'); + assert.equal(await page.evaluate(()=>window.events.filter(e=>e.type==='resync').length),1); + const touch = await page.target().createCDPSession(); + await touch.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:120,y:300}]}); + await touch.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x:120,y:180}]}); + await touch.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]}); + assert.equal(await page.evaluate(()=>document.activeElement?.classList.contains('xterm-helper-textarea')),false,'swiping must not open the keyboard'); + await page.touchscreen.tap(120,120);await page.waitForFunction(()=>document.activeElement?.classList.contains('xterm-helper-textarea'));await page.keyboard.type('pwd');await page.keyboard.press('Enter'); await page.waitForFunction(()=>window.events.filter(e=>e.type==='input').map(e=>e.data).join('')==='pwd\r'); assert.equal(await page.evaluate(()=>!!document.querySelector('.xterm-fg-2')),true); assert.ok(await page.evaluate(()=>window.events.some(e=>e.type==='resize'&&e.cols>0&&e.rows>0))); diff --git a/src/shared/i18n/contract/locales.json b/src/shared/i18n/contract/locales.json index f856723b8f..69b424e024 100644 --- a/src/shared/i18n/contract/locales.json +++ b/src/shared/i18n/contract/locales.json @@ -7,6 +7,7 @@ "zh-CN" ], "surfaceDefaults": { + "android": "zh-CN", "web-ui": "zh-CN", "mobile-web": "en-US", "installer": "en-US", diff --git a/src/shared/terminal/webview/generated/terminal.js b/src/shared/terminal/webview/generated/terminal.js index 7a074ead05..c5ce81fb7a 100644 --- a/src/shared/terminal/webview/generated/terminal.js +++ b/src/shared/terminal/webview/generated/terminal.js @@ -9,17 +9,17 @@ * Fabrice Bellard's javascript vt100 for jslinux: * http://bellard.org/jslinux/ * Copyright (c) 2011 Fabrice Bellard - */var $r=Object.defineProperty,Qn=Object.getOwnPropertyDescriptor,eo=(e,t)=>{for(var i in t)$r(e,i,{get:t[i],enumerable:!0})},K=(e,t,i,s)=>{for(var r=s>1?void 0:s?Qn(t,i):t,n=e.length-1,o;n>=0;n--)(o=e[n])&&(r=(s?o(t,i,r):o(r))||r);return s&&r&&$r(t,i,r),r},w=(e,t)=>(i,s)=>t(i,s,e),Is="Terminal input",Ti={get:()=>Is,set:e=>Is=e},Ns="Too much output to announce, navigate to rows manually to read",Zt={get:()=>Ns,set:e=>Ns=e};function to(e){return e.replace(/\r?\n/g,"\r")}function io(e,t){return t?"\x1B[200~"+e+"\x1B[201~":e}function so(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()}function ro(e,t,i,s){if(e.stopPropagation(),e.clipboardData){let r=e.clipboardData.getData("text/plain");Ur(r,t,i,s)}}function Ur(e,t,i,s){e=to(e),e=io(e,i.decPrivateModes.bracketedPasteMode&&s.rawOptions.ignoreBracketedPasteMode!==!0),i.triggerDataEvent(e,!0),t.value=""}function qr(e,t,i){let s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}function Fs(e,t,i,s,r){qr(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}function Ve(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function at(e,t=0,i=e.length){let s="";for(let r=t;r65535?(n-=65536,s+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):s+=String.fromCharCode(n)}return s}var no=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){let n=e.charCodeAt(r++);56320<=n&&n<=57343?t[s++]=(this._interim-55296)*1024+n-56320+65536:(t[s++]=this._interim,t[s++]=n),this._interim=0}for(let n=r;n=i)return this._interim=o,s;let h=e.charCodeAt(n);56320<=h&&h<=57343?t[s++]=(o-55296)*1024+h-56320+65536:(t[s++]=o,t[s++]=h);continue}o!==65279&&(t[s++]=o)}return s}},oo=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r,n,o,h,l=0,a=0;if(this.interim[0]){let u=!1,f=this.interim[0];f&=(f&224)===192?31:(f&240)===224?15:7;let _=0,p;for(;(p=this.interim[++_]&63)&&_<4;)f<<=6,f|=p;let y=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,E=y-_;for(;a=i)return 0;if(p=e[a++],(p&192)!==128){a--,u=!0;break}else this.interim[_++]=p,f<<=6,f|=p&63}u||(y===2?f<128?a--:t[s++]=f:y===3?f<2048||f>=55296&&f<=57343||f===65279||(t[s++]=f):f<65536||f>1114111||(t[s++]=f)),this.interim.fill(0)}let d=i-4,c=a;for(;c=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(l=(r&31)<<6|n&63,l<128){c--;continue}t[s++]=l}else if((r&240)===224){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(l=(r&15)<<12|(n&63)<<6|o&63,l<2048||l>=55296&&l<=57343||l===65279)continue;t[s++]=l}else if((r&248)===240){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=o,s;if(h=e[c++],(h&192)!==128){c--;continue}if(l=(r&7)<<18|(n&63)<<12|(o&63)<<6|h&63,l<65536||l>1114111)continue;t[s++]=l}}return s}},Pt=class Vr{constructor(){this.fg=0,this.bg=0,this.extended=new Qt}static toColorRGB(t){return[t>>>16&255,t>>>8&255,t&255]}static fromColorRGB(t){return(t[0]&255)<<16|(t[1]&255)<<8|t[2]&255}clone(){let t=new Vr;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},Qt=class Yr{constructor(t=0,i=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=i}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(t){this._ext&=-67108864,this._ext|=t&67108863}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){let t=(this._ext&3758096384)>>29;return t<0?t^4294967288:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}clone(){return new Yr(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},ke=class jr extends Pt{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new Qt,this.combinedData=""}static fromCharData(t){let i=new jr;return i.setFromCharData(t),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?Ve(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let i=!1;if(t[1].length>2)i=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let r=t[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(s-55296)*1024+r-56320+65536|t[2]<<22:i=!0}else i=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;i&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},Hs="di$target",Ai="di$dependencies",pi=new Map;function ao(e){return e[Ai]||[]}function G(e){if(pi.has(e))return pi.get(e);let t=function(i,s,r){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");ho(t,i,r)};return t._id=e,pi.set(e,t),t}function ho(e,t,i){t[Hs]===t?t[Ai].push({id:e,index:i}):(t[Ai]=[{id:e,index:i}],t[Hs]=t)}var ce=G("BufferService"),Xr=G("CoreMouseService"),je=G("CoreService"),lo=G("CharsetService"),Ss=G("InstantiationService"),Gr=G("LogService"),he=G("OptionsService"),Jr=G("OscLinkService"),co=G("UnicodeService"),Tt=G("DecorationService"),Oi=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new ke}provideLinks(e,t){let i=this._bufferService.buffer.lines.get(e-1);if(!i){t(void 0);return}let s=[],r=this._optionsService.rawOptions.linkHandler,n=this._workCell,o=i.getTrimmedLength(),h=-1,l=-1,a=!1;for(let d=0;dr?r.activate(_,p,u):uo(_,p),hover:(_,p)=>r?.hover?.(_,p,u),leave:(_,p)=>r?.leave?.(_,p,u)})}a=!1,n.hasExtendedAttrs()&&n.extended.urlId?(l=d,h=n.extended.urlId):(l=-1,h=-1)}}t(s)}};Oi=K([w(0,ce),w(1,he),w(2,Jr)],Oi);function uo(e,t){if(confirm(`Do you want to navigate to ${t}? + */var qr=Object.defineProperty,io=Object.getOwnPropertyDescriptor,so=(e,t)=>{for(var i in t)qr(e,i,{get:t[i],enumerable:!0})},K=(e,t,i,s)=>{for(var r=s>1?void 0:s?io(t,i):t,n=e.length-1,o;n>=0;n--)(o=e[n])&&(r=(s?o(t,i,r):o(r))||r);return s&&r&&qr(t,i,r),r},w=(e,t)=>(i,s)=>t(i,s,e),Fs="Terminal input",Oi={get:()=>Fs,set:e=>Fs=e},Hs="Too much output to announce, navigate to rows manually to read",ei={get:()=>Hs,set:e=>Hs=e};function ro(e){return e.replace(/\r?\n/g,"\r")}function no(e,t){return t?"\x1B[200~"+e+"\x1B[201~":e}function oo(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()}function ao(e,t,i,s){if(e.stopPropagation(),e.clipboardData){let r=e.clipboardData.getData("text/plain");Vr(r,t,i,s)}}function Vr(e,t,i,s){e=ro(e),e=no(e,i.decPrivateModes.bracketedPasteMode&&s.rawOptions.ignoreBracketedPasteMode!==!0),i.triggerDataEvent(e,!0),t.value=""}function Yr(e,t,i){let s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}function Ws(e,t,i,s,r){Yr(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}function Ye(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function ht(e,t=0,i=e.length){let s="";for(let r=t;r65535?(n-=65536,s+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):s+=String.fromCharCode(n)}return s}var ho=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){let n=e.charCodeAt(r++);56320<=n&&n<=57343?t[s++]=(this._interim-55296)*1024+n-56320+65536:(t[s++]=this._interim,t[s++]=n),this._interim=0}for(let n=r;n=i)return this._interim=o,s;let h=e.charCodeAt(n);56320<=h&&h<=57343?t[s++]=(o-55296)*1024+h-56320+65536:(t[s++]=o,t[s++]=h);continue}o!==65279&&(t[s++]=o)}return s}},lo=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r,n,o,h,l=0,a=0;if(this.interim[0]){let u=!1,f=this.interim[0];f&=(f&224)===192?31:(f&240)===224?15:7;let _=0,p;for(;(p=this.interim[++_]&63)&&_<4;)f<<=6,f|=p;let y=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,E=y-_;for(;a=i)return 0;if(p=e[a++],(p&192)!==128){a--,u=!0;break}else this.interim[_++]=p,f<<=6,f|=p&63}u||(y===2?f<128?a--:t[s++]=f:y===3?f<2048||f>=55296&&f<=57343||f===65279||(t[s++]=f):f<65536||f>1114111||(t[s++]=f)),this.interim.fill(0)}let d=i-4,c=a;for(;c=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(l=(r&31)<<6|n&63,l<128){c--;continue}t[s++]=l}else if((r&240)===224){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(l=(r&15)<<12|(n&63)<<6|o&63,l<2048||l>=55296&&l<=57343||l===65279)continue;t[s++]=l}else if((r&248)===240){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=o,s;if(h=e[c++],(h&192)!==128){c--;continue}if(l=(r&7)<<18|(n&63)<<12|(o&63)<<6|h&63,l<65536||l>1114111)continue;t[s++]=l}}return s}},Tt=class jr{constructor(){this.fg=0,this.bg=0,this.extended=new ti}static toColorRGB(t){return[t>>>16&255,t>>>8&255,t&255]}static fromColorRGB(t){return(t[0]&255)<<16|(t[1]&255)<<8|t[2]&255}clone(){let t=new jr;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},ti=class Xr{constructor(t=0,i=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=i}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(t){this._ext&=-67108864,this._ext|=t&67108863}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){let t=(this._ext&3758096384)>>29;return t<0?t^4294967288:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}clone(){return new Xr(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},ke=class Gr extends Tt{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new ti,this.combinedData=""}static fromCharData(t){let i=new Gr;return i.setFromCharData(t),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?Ye(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let i=!1;if(t[1].length>2)i=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let r=t[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(s-55296)*1024+r-56320+65536|t[2]<<22:i=!0}else i=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;i&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},zs="di$target",Ii="di$dependencies",vi=new Map;function co(e){return e[Ii]||[]}function G(e){if(vi.has(e))return vi.get(e);let t=function(i,s,r){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");uo(t,i,r)};return t._id=e,vi.set(e,t),t}function uo(e,t,i){t[zs]===t?t[Ii].push({id:e,index:i}):(t[Ii]=[{id:e,index:i}],t[zs]=t)}var ce=G("BufferService"),Jr=G("CoreMouseService"),Xe=G("CoreService"),_o=G("CharsetService"),bs=G("InstantiationService"),Zr=G("LogService"),he=G("OptionsService"),Qr=G("OscLinkService"),fo=G("UnicodeService"),At=G("DecorationService"),Ni=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new ke}provideLinks(e,t){let i=this._bufferService.buffer.lines.get(e-1);if(!i){t(void 0);return}let s=[],r=this._optionsService.rawOptions.linkHandler,n=this._workCell,o=i.getTrimmedLength(),h=-1,l=-1,a=!1;for(let d=0;dr?r.activate(_,p,u):po(_,p),hover:(_,p)=>r?.hover?.(_,p,u),leave:(_,p)=>r?.leave?.(_,p,u)})}a=!1,n.hasExtendedAttrs()&&n.extended.urlId?(l=d,h=n.extended.urlId):(l=-1,h=-1)}}t(s)}};Ni=K([w(0,ce),w(1,he),w(2,Qr)],Ni);function po(e,t){if(confirm(`Do you want to navigate to ${t}? -WARNING: This link could potentially be dangerous`)){let i=window.open();if(i){try{i.opener=null}catch{}i.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}var hi=G("CharSizeService"),Ie=G("CoreBrowserService"),ws=G("MouseService"),Ne=G("RenderService"),_o=G("SelectionService"),Zr=G("CharacterJoinerService"),ht=G("ThemeService"),Qr=G("LinkProviderService"),fo=G("KeyboardService");function q(e){return{dispose:e}}function Ii(e){if(e)if(Array.isArray(e))for(let t of e)t.dispose();else e.dispose()}var bs=class{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(let e of this._disposables)e.dispose();this._disposables.clear()}}},I=class{constructor(){this._store=new bs}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}},ot=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}},me=typeof window=="object"?window:globalThis,po=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?Ws.isErrorNoTelemetry(e)?new Ws(e.message+` +WARNING: This link could potentially be dangerous`)){let i=window.open();if(i){try{i.opener=null}catch{}i.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}var ci=G("CharSizeService"),Ne=G("CoreBrowserService"),ys=G("MouseService"),Fe=G("RenderService"),go=G("SelectionService"),en=G("CharacterJoinerService"),lt=G("ThemeService"),tn=G("LinkProviderService"),vo=G("KeyboardService");function q(e){return{dispose:e}}function Fi(e){if(e)if(Array.isArray(e))for(let t of e)t.dispose();else e.dispose()}var Cs=class{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(let e of this._disposables)e.dispose();this._disposables.clear()}}},I=class{constructor(){this._store=new Cs}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}},at=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}},me=typeof window=="object"?window:globalThis,mo=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?Ks.isErrorNoTelemetry(e)?new Ks(e.message+` `+e.stack):new Error(e.message+` -`+e.stack):e},0)}}addListener(e){return this.listeners.push(e),()=>{this._removeListener(e)}}emit(e){this.listeners.forEach(t=>{t(e)})}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}},go=new po;function Kt(e){vo(e)||go.onUnexpectedError(e)}var Ni="Canceled";function vo(e){return e instanceof mo?!0:e instanceof Error&&e.name===Ni&&e.message===Ni}var mo=class extends Error{constructor(){super(Ni),this.name=this.message}};function So(e){return e?new Error(`Illegal argument: ${e}`):new Error("Illegal argument")}var Ws=class Fi extends Error{constructor(t){super(t),this.name="CodeExpectedError"}static fromError(t){if(t instanceof Fi)return t;let i=new Fi;return i.message=t.message,i.stack=t.stack,i}static isErrorNoTelemetry(t){return t.name==="CodeExpectedError"}},Hi=class en extends Error{constructor(t){super(t||"An unexpected bug occurred."),Object.setPrototypeOf(this,en.prototype)}};function wo(e,t){let i=this,s=!1,r;return function(){if(s)return r;if(s=!0,t)try{r=e.apply(i,arguments)}finally{t()}else r=e.apply(i,arguments);return r}}function bo(e,t,i=0,s=e.length){let r=i,n=s;for(;r{function t(n){return n<0}e.isLessThan=t;function i(n){return n<=0}e.isLessThanOrEqual=i;function s(n){return n>0}e.isGreaterThan=s;function r(n){return n===0}e.isNeitherLessOrGreaterThan=r,e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0})(sn||(sn={}));function Co(e,t){return(i,s)=>t(e(i),e(s))}var ko=(e,t)=>e-t,zs=class Wi{constructor(t){this.iterate=t}forEach(t){this.iterate(i=>(t(i),!0))}toArray(){let t=[];return this.iterate(i=>(t.push(i),!0)),t}filter(t){return new Wi(i=>this.iterate(s=>t(s)?i(s):!0))}map(t){return new Wi(i=>this.iterate(s=>i(t(s))))}some(t){let i=!1;return this.iterate(s=>(i=t(s),!i)),i}findFirst(t){let i;return this.iterate(s=>t(s)?(i=s,!1):!0),i}findLast(t){let i;return this.iterate(s=>(t(s)&&(i=s),!0)),i}findLastMaxBy(t){let i,s=!0;return this.iterate(r=>((s||sn.isGreaterThan(t(r,i)))&&(s=!1,i=r),!0)),i}};zs.empty=new zs(e=>{});function xo(e,t){let i=Object.create(null);for(let s of e){let r=t(s),n=i[r];n||(n=i[r]=[]),n.push(s)}return i}var Ks,$s,zl=class{constructor(e,t){this.toKey=t,this._map=new Map,this[Ks]="SetWithKey";for(let i of e)this.add(i)}get size(){return this._map.size}add(e){let t=this.toKey(e);return this._map.set(t,e),this}delete(e){return this._map.delete(this.toKey(e))}has(e){return this._map.has(this.toKey(e))}*entries(){for(let e of this._map.values())yield[e,e]}keys(){return this.values()}*values(){for(let e of this._map.values())yield e}clear(){this._map.clear()}forEach(e,t){this._map.forEach(i=>e.call(t,i,i,this))}[($s=Symbol.iterator,Ks=Symbol.toStringTag,$s)](){return this.values()}},Eo=class{constructor(){this.map=new Map}add(e,t){let i=this.map.get(e);i||(i=new Set,this.map.set(e,i)),i.add(t)}delete(e,t){let i=this.map.get(e);i&&(i.delete(t),i.size===0&&this.map.delete(e))}forEach(e,t){let i=this.map.get(e);i&&i.forEach(t)}get(e){return this.map.get(e)||new Set}},rn;(e=>{function t(x){return x&&typeof x=="object"&&typeof x[Symbol.iterator]=="function"}e.is=t;let i=Object.freeze([]);function s(){return i}e.empty=s;function*r(x){yield x}e.single=r;function n(x){return t(x)?x:r(x)}e.wrap=n;function o(x){return x||i}e.from=o;function*h(x){for(let L=x.length-1;L>=0;L--)yield x[L]}e.reverse=h;function l(x){return!x||x[Symbol.iterator]().next().done===!0}e.isEmpty=l;function a(x){return x[Symbol.iterator]().next().value}e.first=a;function d(x,L){let P=0;for(let re of x)if(L(re,P++))return!0;return!1}e.some=d;function c(x,L){for(let P of x)if(L(P))return P}e.find=c;function*u(x,L){for(let P of x)L(P)&&(yield P)}e.filter=u;function*f(x,L){let P=0;for(let re of x)yield L(re,P++)}e.map=f;function*_(x,L){let P=0;for(let re of x)yield*L(re,P++)}e.flatMap=_;function*p(...x){for(let L of x)yield*L}e.concat=p;function y(x,L,P){let re=P;for(let ye of x)re=L(re,ye);return re}e.reduce=y;function*E(x,L,P=x.length){for(L<0&&(L+=x.length),P<0?P+=x.length:P>x.length&&(P=x.length);Li.source!==null&&!this.getRootParent(i,t).isSingleton).flatMap(([i])=>i)}computeLeakingDisposables(t=10,i){let s;if(i)s=i;else{let l=new Map,a=[...this.livingDisposables.values()].filter(c=>c.source!==null&&!this.getRootParent(c,l).isSingleton);if(a.length===0)return;let d=new Set(a.map(c=>c.value));if(s=a.filter(c=>!(c.parent&&d.has(c.parent))),s.length===0)throw new Error("There are cyclic diposable chains!")}if(!s)return;function r(l){function a(c,u){for(;c.length>0&&u.some(f=>typeof f=="string"?f===c[0]:c[0].match(f));)c.shift()}let d=l.source.split(` -`).map(c=>c.trim().replace("at ","")).filter(c=>c!=="");return a(d,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),d.reverse()}let n=new Eo;for(let l of s){let a=r(l);for(let d=0;d<=a.length;d++)n.add(a.slice(0,d).join(` -`),l)}s.sort(Co(l=>l.idx,ko));let o="",h=0;for(let l of s.slice(0,t)){h++;let a=r(l),d=[];for(let c=0;c{this._removeListener(e)}}emit(e){this.listeners.forEach(t=>{t(e)})}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}},So=new mo;function Ut(e){wo(e)||So.onUnexpectedError(e)}var Hi="Canceled";function wo(e){return e instanceof bo?!0:e instanceof Error&&e.name===Hi&&e.message===Hi}var bo=class extends Error{constructor(){super(Hi),this.name=this.message}};function yo(e){return e?new Error(`Illegal argument: ${e}`):new Error("Illegal argument")}var Ks=class Wi extends Error{constructor(t){super(t),this.name="CodeExpectedError"}static fromError(t){if(t instanceof Wi)return t;let i=new Wi;return i.message=t.message,i.stack=t.stack,i}static isErrorNoTelemetry(t){return t.name==="CodeExpectedError"}},zi=class sn extends Error{constructor(t){super(t||"An unexpected bug occurred."),Object.setPrototypeOf(this,sn.prototype)}};function Co(e,t){let i=this,s=!1,r;return function(){if(s)return r;if(s=!0,t)try{r=e.apply(i,arguments)}finally{t()}else r=e.apply(i,arguments);return r}}function ko(e,t,i=0,s=e.length){let r=i,n=s;for(;r{function t(n){return n<0}e.isLessThan=t;function i(n){return n<=0}e.isLessThanOrEqual=i;function s(n){return n>0}e.isGreaterThan=s;function r(n){return n===0}e.isNeitherLessOrGreaterThan=r,e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0})(nn||(nn={}));function Eo(e,t){return(i,s)=>t(e(i),e(s))}var Bo=(e,t)=>e-t,$s=class Ki{constructor(t){this.iterate=t}forEach(t){this.iterate(i=>(t(i),!0))}toArray(){let t=[];return this.iterate(i=>(t.push(i),!0)),t}filter(t){return new Ki(i=>this.iterate(s=>t(s)?i(s):!0))}map(t){return new Ki(i=>this.iterate(s=>i(t(s))))}some(t){let i=!1;return this.iterate(s=>(i=t(s),!i)),i}findFirst(t){let i;return this.iterate(s=>t(s)?(i=s,!1):!0),i}findLast(t){let i;return this.iterate(s=>(t(s)&&(i=s),!0)),i}findLastMaxBy(t){let i,s=!0;return this.iterate(r=>((s||nn.isGreaterThan(t(r,i)))&&(s=!1,i=r),!0)),i}};$s.empty=new $s(e=>{});function Do(e,t){let i=Object.create(null);for(let s of e){let r=t(s),n=i[r];n||(n=i[r]=[]),n.push(s)}return i}var Us,qs,Ul=class{constructor(e,t){this.toKey=t,this._map=new Map,this[Us]="SetWithKey";for(let i of e)this.add(i)}get size(){return this._map.size}add(e){let t=this.toKey(e);return this._map.set(t,e),this}delete(e){return this._map.delete(this.toKey(e))}has(e){return this._map.has(this.toKey(e))}*entries(){for(let e of this._map.values())yield[e,e]}keys(){return this.values()}*values(){for(let e of this._map.values())yield e}clear(){this._map.clear()}forEach(e,t){this._map.forEach(i=>e.call(t,i,i,this))}[(qs=Symbol.iterator,Us=Symbol.toStringTag,qs)](){return this.values()}},Ro=class{constructor(){this.map=new Map}add(e,t){let i=this.map.get(e);i||(i=new Set,this.map.set(e,i)),i.add(t)}delete(e,t){let i=this.map.get(e);i&&(i.delete(t),i.size===0&&this.map.delete(e))}forEach(e,t){let i=this.map.get(e);i&&i.forEach(t)}get(e){return this.map.get(e)||new Set}},on;(e=>{function t(x){return x&&typeof x=="object"&&typeof x[Symbol.iterator]=="function"}e.is=t;let i=Object.freeze([]);function s(){return i}e.empty=s;function*r(x){yield x}e.single=r;function n(x){return t(x)?x:r(x)}e.wrap=n;function o(x){return x||i}e.from=o;function*h(x){for(let L=x.length-1;L>=0;L--)yield x[L]}e.reverse=h;function l(x){return!x||x[Symbol.iterator]().next().done===!0}e.isEmpty=l;function a(x){return x[Symbol.iterator]().next().value}e.first=a;function d(x,L){let P=0;for(let re of x)if(L(re,P++))return!0;return!1}e.some=d;function c(x,L){for(let P of x)if(L(P))return P}e.find=c;function*u(x,L){for(let P of x)L(P)&&(yield P)}e.filter=u;function*f(x,L){let P=0;for(let re of x)yield L(re,P++)}e.map=f;function*_(x,L){let P=0;for(let re of x)yield*L(re,P++)}e.flatMap=_;function*p(...x){for(let L of x)yield*L}e.concat=p;function y(x,L,P){let re=P;for(let ye of x)re=L(re,ye);return re}e.reduce=y;function*E(x,L,P=x.length){for(L<0&&(L+=x.length),P<0?P+=x.length:P>x.length&&(P=x.length);Li.source!==null&&!this.getRootParent(i,t).isSingleton).flatMap(([i])=>i)}computeLeakingDisposables(t=10,i){let s;if(i)s=i;else{let l=new Map,a=[...this.livingDisposables.values()].filter(c=>c.source!==null&&!this.getRootParent(c,l).isSingleton);if(a.length===0)return;let d=new Set(a.map(c=>c.value));if(s=a.filter(c=>!(c.parent&&d.has(c.parent))),s.length===0)throw new Error("There are cyclic diposable chains!")}if(!s)return;function r(l){function a(c,u){for(;c.length>0&&u.some(f=>typeof f=="string"?f===c[0]:c[0].match(f));)c.shift()}let d=l.source.split(` +`).map(c=>c.trim().replace("at ","")).filter(c=>c!=="");return a(d,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),d.reverse()}let n=new Ro;for(let l of s){let a=r(l);for(let d=0;d<=a.length;d++)n.add(a.slice(0,d).join(` +`),l)}s.sort(Eo(l=>l.idx,Bo));let o="",h=0;for(let l of s.slice(0,t)){h++;let a=r(l),d=[];for(let c=0;cr(p)[c]),p=>p);delete _[a[c]];for(let[p,y]of Object.entries(_))d.unshift(` - stacktraces of ${y.length} other leaks continue with ${p}`);d.unshift(u)}o+=` +`)),_=Do([...f].map(p=>r(p)[c]),p=>p);delete _[a[c]];for(let[p,y]of Object.entries(_))d.unshift(` - stacktraces of ${y.length} other leaks continue with ${p}`);d.unshift(u)}o+=` ==================== Leaking disposable ${h}/${s.length}: ${l.value.constructor.name} ==================== @@ -32,19 +32,19 @@ ${d.join(` ... and ${s.length-t} more leaking disposables -`),{leaks:s,details:o}}};Do.idx=0;function Ro(e){tt=e}if(Bo){let e="__is_disposable_tracked__";Ro(new class{trackDisposable(t){let i=new Error("Potentially leaked disposable").stack;setTimeout(()=>{t[e]||console.log(i)},3e3)}setParent(t,i){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsDisposed(t){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsSingleton(t){}})}function ys(e){return tt?.trackDisposable(e),e}function Cs(e){tt?.markAsDisposed(e)}function zi(e,t){tt?.setParent(e,t)}function Mo(e,t){if(tt)for(let i of e)tt.setParent(i,t)}function Us(e){return tt?.markAsSingleton(e),e}function ei(e){if(rn.is(e)){let t=[];for(let i of e)if(i)try{i.dispose()}catch(s){t.push(s)}if(t.length===1)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(e)?[]:e}else if(e)return e.dispose(),e}function Lo(...e){let t=Ye(()=>ei(e));return Mo(e,t),t}function Ye(e){let t=ys({dispose:wo(()=>{Cs(t),e()})});return t}var on=class an{constructor(){this._toDispose=new Set,this._isDisposed=!1,ys(this)}dispose(){this._isDisposed||(Cs(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{ei(this._toDispose)}finally{this._toDispose.clear()}}add(t){if(!t)return t;if(t===this)throw new Error("Cannot register a disposable on itself!");return zi(t,this),this._isDisposed?an.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(t),t}delete(t){if(t){if(t===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(t),t.dispose()}}deleteAndLeak(t){t&&this._toDispose.has(t)&&(this._toDispose.delete(t),zi(t,null))}};on.DISABLE_DISPOSED_WARNING=!1;var et=on,Se=class{constructor(){this._store=new et,ys(this),zi(this._store,this)}dispose(){Cs(this),this._store.dispose()}_register(e){if(e===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(e)}};Se.None=Object.freeze({dispose(){}});var Ki=class $i{constructor(t){this.element=t,this.next=$i.Undefined,this.prev=$i.Undefined}};Ki.Undefined=new Ki(void 0);var W=Ki,qs=class{constructor(){this._first=W.Undefined,this._last=W.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===W.Undefined}clear(){let e=this._first;for(;e!==W.Undefined;){let t=e.next;e.prev=W.Undefined,e.next=W.Undefined,e=t}this._first=W.Undefined,this._last=W.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){let i=new W(e);if(this._first===W.Undefined)this._first=i,this._last=i;else if(t){let r=this._last;this._last=i,i.prev=r,r.next=i}else{let r=this._first;this._first=i,i.next=r,r.prev=i}this._size+=1;let s=!1;return()=>{s||(s=!0,this._remove(i))}}shift(){if(this._first!==W.Undefined){let e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==W.Undefined){let e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==W.Undefined&&e.next!==W.Undefined){let t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===W.Undefined&&e.next===W.Undefined?(this._first=W.Undefined,this._last=W.Undefined):e.next===W.Undefined?(this._last=this._last.prev,this._last.next=W.Undefined):e.prev===W.Undefined&&(this._first=this._first.next,this._first.prev=W.Undefined);this._size-=1}*[Symbol.iterator](){let e=this._first;for(;e!==W.Undefined;)yield e.element,e=e.next}},Po=globalThis.performance&&typeof globalThis.performance.now=="function",To=class hn{static create(t){return new hn(t)}constructor(t){this._now=Po&&t===!1?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return this._stopTime!==-1?this._stopTime-this._startTime:this._now()-this._startTime}},Ao=!1,Vs=!1,Oo=!1,li;(e=>{e.None=()=>Se.None;function t(m){if(Oo){let{onDidAddListener:g}=m,C=Vi.create(),S=0;m.onDidAddListener=()=>{++S===2&&(console.warn("snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here"),C.print()),g?.()}}}function i(m,g){return u(m,()=>{},0,void 0,!0,void 0,g)}e.defer=i;function s(m){return(g,C=null,S)=>{let k=!1,v;return v=m(M=>{if(!k)return v?v.dispose():k=!0,g.call(C,M)},null,S),k&&v.dispose(),v}}e.once=s;function r(m,g,C){return d((S,k=null,v)=>m(M=>S.call(k,g(M)),null,v),C)}e.map=r;function n(m,g,C){return d((S,k=null,v)=>m(M=>{g(M),S.call(k,M)},null,v),C)}e.forEach=n;function o(m,g,C){return d((S,k=null,v)=>m(M=>g(M)&&S.call(k,M),null,v),C)}e.filter=o;function h(m){return m}e.signal=h;function l(...m){return(g,C=null,S)=>{let k=Lo(...m.map(v=>v(M=>g.call(C,M))));return c(k,S)}}e.any=l;function a(m,g,C,S){let k=C;return r(m,v=>(k=g(k,v),k),S)}e.reduce=a;function d(m,g){let C,S={onWillAddFirstListener(){C=m(k.fire,k)},onDidRemoveLastListener(){C?.dispose()}};g||t(S);let k=new se(S);return g?.add(k),k.event}function c(m,g){return g instanceof Array?g.push(m):g&&g.add(m),m}function u(m,g,C=100,S=!1,k=!1,v,M){let H,ne,Ee,Le=0,J,He={leakWarningThreshold:v,onWillAddFirstListener(){H=m(Xe=>{Le++,ne=g(ne,Xe),S&&!Ee&&(le.fire(ne),ne=void 0),J=()=>{let dt=ne;ne=void 0,Ee=void 0,(!S||Le>1)&&le.fire(dt),Le=0},typeof C=="number"?(clearTimeout(Ee),Ee=setTimeout(J,C)):Ee===void 0&&(Ee=0,queueMicrotask(J))})},onWillRemoveListener(){k&&Le>0&&J?.()},onDidRemoveLastListener(){J=void 0,H.dispose()}};M||t(He);let le=new se(He);return M?.add(le),le.event}e.debounce=u;function f(m,g=0,C){return e.debounce(m,(S,k)=>S?(S.push(k),S):[k],g,void 0,!0,void 0,C)}e.accumulate=f;function _(m,g=(S,k)=>S===k,C){let S=!0,k;return o(m,v=>{let M=S||!g(v,k);return S=!1,k=v,M},C)}e.latch=_;function p(m,g,C){return[e.filter(m,g,C),e.filter(m,S=>!g(S),C)]}e.split=p;function y(m,g=!1,C=[],S){let k=C.slice(),v=m(ne=>{k?k.push(ne):H.fire(ne)});S&&S.add(v);let M=()=>{k?.forEach(ne=>H.fire(ne)),k=null},H=new se({onWillAddFirstListener(){v||(v=m(ne=>H.fire(ne)),S&&S.add(v))},onDidAddFirstListener(){k&&(g?setTimeout(M):M())},onDidRemoveLastListener(){v&&v.dispose(),v=null}});return S&&S.add(H),H.event}e.buffer=y;function E(m,g){return(C,S,k)=>{let v=g(new R);return m(function(M){let H=v.evaluate(M);H!==A&&C.call(S,H)},void 0,k)}}e.chain=E;let A=Symbol("HaltChainable");class R{constructor(){this.steps=[]}map(g){return this.steps.push(g),this}forEach(g){return this.steps.push(C=>(g(C),C)),this}filter(g){return this.steps.push(C=>g(C)?C:A),this}reduce(g,C){let S=C;return this.steps.push(k=>(S=g(S,k),S)),this}latch(g=(C,S)=>C===S){let C=!0,S;return this.steps.push(k=>{let v=C||!g(k,S);return C=!1,S=k,v?k:A}),this}evaluate(g){for(let C of this.steps)if(g=C(g),g===A)break;return g}}function x(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.on(g,S),v=()=>m.removeListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromNodeEventEmitter=x;function L(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.addEventListener(g,S),v=()=>m.removeEventListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromDOMEventEmitter=L;function P(m){return new Promise(g=>s(m)(g))}e.toPromise=P;function re(m){let g=new se;return m.then(C=>{g.fire(C)},()=>{g.fire(void 0)}).finally(()=>{g.dispose()}),g.event}e.fromPromise=re;function ye(m,g){return m(C=>g.fire(C))}e.forward=ye;function xe(m,g,C){return g(C),m(S=>g(S))}e.runAndSubscribe=xe;class Ot{constructor(g,C){this._observable=g,this._counter=0,this._hasChanged=!1;let S={onWillAddFirstListener:()=>{g.addObserver(this)},onDidRemoveLastListener:()=>{g.removeObserver(this)}};C||t(S),this.emitter=new se(S),C&&C.add(this.emitter)}beginUpdate(g){this._counter++}handlePossibleChange(g){}handleChange(g,C){this._hasChanged=!0}endUpdate(g){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function F(m,g){return new Ot(m,g).emitter.event}e.fromObservable=F;function _i(m){return(g,C,S)=>{let k=0,v=!1,M={beginUpdate(){k++},endUpdate(){k--,k===0&&(m.reportChanges(),v&&(v=!1,g.call(C)))},handlePossibleChange(){},handleChange(){v=!0}};m.addObserver(M),m.reportChanges();let H={dispose(){m.removeObserver(M)}};return S instanceof et?S.add(H):Array.isArray(S)&&S.push(H),H}}e.fromObservableLight=_i})(li||(li={}));var Ui=class qi{constructor(t){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${t}_${qi._idPool++}`,qi.all.add(this)}start(t){this._stopWatch=new To,this.listenerCount=t}stop(){if(this._stopWatch){let t=this._stopWatch.elapsed();this.durations.push(t),this.elapsedOverall+=t,this.invocationCount+=1,this._stopWatch=void 0}}};Ui.all=new Set,Ui._idPool=0;var Io=Ui,Ys=-1,ln=class cn{constructor(t,i,s=(cn._idPool++).toString(16).padStart(3,"0")){this._errorHandler=t,this.threshold=i,this.name=s,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(t,i){let s=this.threshold;if(s<=0||i{let n=this._stacks.get(t.value)||0;this._stacks.set(t.value,n-1)}}getMostFrequentStack(){if(!this._stacks)return;let t,i=0;for(let[s,r]of this._stacks)(!t||i{t[e]||console.log(i)},3e3)}setParent(t,i){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsDisposed(t){if(t&&t!==Se.None)try{t[e]=!0}catch{}}markAsSingleton(t){}})}function ks(e){return it?.trackDisposable(e),e}function xs(e){it?.markAsDisposed(e)}function $i(e,t){it?.setParent(e,t)}function To(e,t){if(it)for(let i of e)it.setParent(i,t)}function Vs(e){return it?.markAsSingleton(e),e}function ii(e){if(on.is(e)){let t=[];for(let i of e)if(i)try{i.dispose()}catch(s){t.push(s)}if(t.length===1)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(e)?[]:e}else if(e)return e.dispose(),e}function Ao(...e){let t=je(()=>ii(e));return To(e,t),t}function je(e){let t=ks({dispose:Co(()=>{xs(t),e()})});return t}var hn=class ln{constructor(){this._toDispose=new Set,this._isDisposed=!1,ks(this)}dispose(){this._isDisposed||(xs(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{ii(this._toDispose)}finally{this._toDispose.clear()}}add(t){if(!t)return t;if(t===this)throw new Error("Cannot register a disposable on itself!");return $i(t,this),this._isDisposed?ln.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(t),t}delete(t){if(t){if(t===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(t),t.dispose()}}deleteAndLeak(t){t&&this._toDispose.has(t)&&(this._toDispose.delete(t),$i(t,null))}};hn.DISABLE_DISPOSED_WARNING=!1;var tt=hn,Se=class{constructor(){this._store=new tt,ks(this),$i(this._store,this)}dispose(){xs(this),this._store.dispose()}_register(e){if(e===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(e)}};Se.None=Object.freeze({dispose(){}});var Ui=class qi{constructor(t){this.element=t,this.next=qi.Undefined,this.prev=qi.Undefined}};Ui.Undefined=new Ui(void 0);var W=Ui,Ys=class{constructor(){this._first=W.Undefined,this._last=W.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===W.Undefined}clear(){let e=this._first;for(;e!==W.Undefined;){let t=e.next;e.prev=W.Undefined,e.next=W.Undefined,e=t}this._first=W.Undefined,this._last=W.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){let i=new W(e);if(this._first===W.Undefined)this._first=i,this._last=i;else if(t){let r=this._last;this._last=i,i.prev=r,r.next=i}else{let r=this._first;this._first=i,i.next=r,r.prev=i}this._size+=1;let s=!1;return()=>{s||(s=!0,this._remove(i))}}shift(){if(this._first!==W.Undefined){let e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==W.Undefined){let e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==W.Undefined&&e.next!==W.Undefined){let t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===W.Undefined&&e.next===W.Undefined?(this._first=W.Undefined,this._last=W.Undefined):e.next===W.Undefined?(this._last=this._last.prev,this._last.next=W.Undefined):e.prev===W.Undefined&&(this._first=this._first.next,this._first.prev=W.Undefined);this._size-=1}*[Symbol.iterator](){let e=this._first;for(;e!==W.Undefined;)yield e.element,e=e.next}},Oo=globalThis.performance&&typeof globalThis.performance.now=="function",Io=class cn{static create(t){return new cn(t)}constructor(t){this._now=Oo&&t===!1?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return this._stopTime!==-1?this._stopTime-this._startTime:this._now()-this._startTime}},No=!1,js=!1,Fo=!1,di;(e=>{e.None=()=>Se.None;function t(m){if(Fo){let{onDidAddListener:g}=m,C=ji.create(),S=0;m.onDidAddListener=()=>{++S===2&&(console.warn("snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here"),C.print()),g?.()}}}function i(m,g){return u(m,()=>{},0,void 0,!0,void 0,g)}e.defer=i;function s(m){return(g,C=null,S)=>{let k=!1,v;return v=m(M=>{if(!k)return v?v.dispose():k=!0,g.call(C,M)},null,S),k&&v.dispose(),v}}e.once=s;function r(m,g,C){return d((S,k=null,v)=>m(M=>S.call(k,g(M)),null,v),C)}e.map=r;function n(m,g,C){return d((S,k=null,v)=>m(M=>{g(M),S.call(k,M)},null,v),C)}e.forEach=n;function o(m,g,C){return d((S,k=null,v)=>m(M=>g(M)&&S.call(k,M),null,v),C)}e.filter=o;function h(m){return m}e.signal=h;function l(...m){return(g,C=null,S)=>{let k=Ao(...m.map(v=>v(M=>g.call(C,M))));return c(k,S)}}e.any=l;function a(m,g,C,S){let k=C;return r(m,v=>(k=g(k,v),k),S)}e.reduce=a;function d(m,g){let C,S={onWillAddFirstListener(){C=m(k.fire,k)},onDidRemoveLastListener(){C?.dispose()}};g||t(S);let k=new se(S);return g?.add(k),k.event}function c(m,g){return g instanceof Array?g.push(m):g&&g.add(m),m}function u(m,g,C=100,S=!1,k=!1,v,M){let H,ne,Ee,Pe=0,J,We={leakWarningThreshold:v,onWillAddFirstListener(){H=m(Ge=>{Pe++,ne=g(ne,Ge),S&&!Ee&&(le.fire(ne),ne=void 0),J=()=>{let ut=ne;ne=void 0,Ee=void 0,(!S||Pe>1)&&le.fire(ut),Pe=0},typeof C=="number"?(clearTimeout(Ee),Ee=setTimeout(J,C)):Ee===void 0&&(Ee=0,queueMicrotask(J))})},onWillRemoveListener(){k&&Pe>0&&J?.()},onDidRemoveLastListener(){J=void 0,H.dispose()}};M||t(We);let le=new se(We);return M?.add(le),le.event}e.debounce=u;function f(m,g=0,C){return e.debounce(m,(S,k)=>S?(S.push(k),S):[k],g,void 0,!0,void 0,C)}e.accumulate=f;function _(m,g=(S,k)=>S===k,C){let S=!0,k;return o(m,v=>{let M=S||!g(v,k);return S=!1,k=v,M},C)}e.latch=_;function p(m,g,C){return[e.filter(m,g,C),e.filter(m,S=>!g(S),C)]}e.split=p;function y(m,g=!1,C=[],S){let k=C.slice(),v=m(ne=>{k?k.push(ne):H.fire(ne)});S&&S.add(v);let M=()=>{k?.forEach(ne=>H.fire(ne)),k=null},H=new se({onWillAddFirstListener(){v||(v=m(ne=>H.fire(ne)),S&&S.add(v))},onDidAddFirstListener(){k&&(g?setTimeout(M):M())},onDidRemoveLastListener(){v&&v.dispose(),v=null}});return S&&S.add(H),H.event}e.buffer=y;function E(m,g){return(C,S,k)=>{let v=g(new R);return m(function(M){let H=v.evaluate(M);H!==A&&C.call(S,H)},void 0,k)}}e.chain=E;let A=Symbol("HaltChainable");class R{constructor(){this.steps=[]}map(g){return this.steps.push(g),this}forEach(g){return this.steps.push(C=>(g(C),C)),this}filter(g){return this.steps.push(C=>g(C)?C:A),this}reduce(g,C){let S=C;return this.steps.push(k=>(S=g(S,k),S)),this}latch(g=(C,S)=>C===S){let C=!0,S;return this.steps.push(k=>{let v=C||!g(k,S);return C=!1,S=k,v?k:A}),this}evaluate(g){for(let C of this.steps)if(g=C(g),g===A)break;return g}}function x(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.on(g,S),v=()=>m.removeListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromNodeEventEmitter=x;function L(m,g,C=S=>S){let S=(...H)=>M.fire(C(...H)),k=()=>m.addEventListener(g,S),v=()=>m.removeEventListener(g,S),M=new se({onWillAddFirstListener:k,onDidRemoveLastListener:v});return M.event}e.fromDOMEventEmitter=L;function P(m){return new Promise(g=>s(m)(g))}e.toPromise=P;function re(m){let g=new se;return m.then(C=>{g.fire(C)},()=>{g.fire(void 0)}).finally(()=>{g.dispose()}),g.event}e.fromPromise=re;function ye(m,g){return m(C=>g.fire(C))}e.forward=ye;function xe(m,g,C){return g(C),m(S=>g(S))}e.runAndSubscribe=xe;class Nt{constructor(g,C){this._observable=g,this._counter=0,this._hasChanged=!1;let S={onWillAddFirstListener:()=>{g.addObserver(this)},onDidRemoveLastListener:()=>{g.removeObserver(this)}};C||t(S),this.emitter=new se(S),C&&C.add(this.emitter)}beginUpdate(g){this._counter++}handlePossibleChange(g){}handleChange(g,C){this._hasChanged=!0}endUpdate(g){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function F(m,g){return new Nt(m,g).emitter.event}e.fromObservable=F;function pi(m){return(g,C,S)=>{let k=0,v=!1,M={beginUpdate(){k++},endUpdate(){k--,k===0&&(m.reportChanges(),v&&(v=!1,g.call(C)))},handlePossibleChange(){},handleChange(){v=!0}};m.addObserver(M),m.reportChanges();let H={dispose(){m.removeObserver(M)}};return S instanceof tt?S.add(H):Array.isArray(S)&&S.push(H),H}}e.fromObservableLight=pi})(di||(di={}));var Vi=class Yi{constructor(t){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${t}_${Yi._idPool++}`,Yi.all.add(this)}start(t){this._stopWatch=new Io,this.listenerCount=t}stop(){if(this._stopWatch){let t=this._stopWatch.elapsed();this.durations.push(t),this.elapsedOverall+=t,this.invocationCount+=1,this._stopWatch=void 0}}};Vi.all=new Set,Vi._idPool=0;var Ho=Vi,Xs=-1,dn=class un{constructor(t,i,s=(un._idPool++).toString(16).padStart(3,"0")){this._errorHandler=t,this.threshold=i,this.name=s,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(t,i){let s=this.threshold;if(s<=0||i{let n=this._stacks.get(t.value)||0;this._stacks.set(t.value,n-1)}}getMostFrequentStack(){if(!this._stacks)return;let t,i=0;for(let[s,r]of this._stacks)(!t||i{if(e instanceof $t)t(e);else for(let i=0;i{e.length!==0&&(console.warn("[LEAKING LISTENERS] GC'ed these listeners that were NOT yet disposed:"),console.warn(e.join(` -`)),e.length=0)},3e3),Ut=new FinalizationRegistry(t=>{typeof t=="string"&&e.push(t)})}var se=class{constructor(e){this._size=0,this._options=e,this._leakageMon=Ys>0||this._options?.leakWarningThreshold?new No(e?.onListenerError??Kt,this._options?.leakWarningThreshold??Ys):void 0,this._perfMon=this._options?._profName?new Io(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){if(!this._disposed){if(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners){if(Vs){let e=this._listeners;queueMicrotask(()=>{Ko(e,t=>t.stack?.print())})}this._listeners=void 0,this._size=0}this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose()}}get event(){return this._event??(this._event=(e,t,i)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let h=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(h);let l=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],a=new Ho(`${h}. HINT: Stack shows most frequent listener (${l[1]}-times)`,l[0]);return(this._options?.onListenerError||Kt)(a),Se.None}if(this._disposed)return Se.None;t&&(e=e.bind(t));let s=new $t(e),r,n;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(s.stack=Vi.create(),r=this._leakageMon.check(s.stack,this._size+1)),Vs&&(s.stack=n??Vi.create()),this._listeners?this._listeners instanceof $t?(this._deliveryQueue??(this._deliveryQueue=new $o),this._listeners=[this._listeners,s]):this._listeners.push(s):(this._options?.onWillAddFirstListener?.(this),this._listeners=s,this._options?.onDidAddFirstListener?.(this)),this._size++;let o=Ye(()=>{Ut?.unregister(o),r?.(),this._removeListener(s)});if(i instanceof et?i.add(o):Array.isArray(i)&&i.push(o),Ut){let h=new Error().stack.split(` +`))}},zo=class extends Error{constructor(e,t){super(e),this.name="ListenerLeakError",this.stack=t}},Ko=class extends Error{constructor(e,t){super(e),this.name="ListenerRefusalError",this.stack=t}},$o=0,qt=class{constructor(e){this.value=e,this.id=$o++}},Uo=2,qo=(e,t)=>{if(e instanceof qt)t(e);else for(let i=0;i{e.length!==0&&(console.warn("[LEAKING LISTENERS] GC'ed these listeners that were NOT yet disposed:"),console.warn(e.join(` +`)),e.length=0)},3e3),Vt=new FinalizationRegistry(t=>{typeof t=="string"&&e.push(t)})}var se=class{constructor(e){this._size=0,this._options=e,this._leakageMon=Xs>0||this._options?.leakWarningThreshold?new Wo(e?.onListenerError??Ut,this._options?.leakWarningThreshold??Xs):void 0,this._perfMon=this._options?._profName?new Ho(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){if(!this._disposed){if(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners){if(js){let e=this._listeners;queueMicrotask(()=>{qo(e,t=>t.stack?.print())})}this._listeners=void 0,this._size=0}this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose()}}get event(){return this._event??(this._event=(e,t,i)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let h=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(h);let l=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],a=new Ko(`${h}. HINT: Stack shows most frequent listener (${l[1]}-times)`,l[0]);return(this._options?.onListenerError||Ut)(a),Se.None}if(this._disposed)return Se.None;t&&(e=e.bind(t));let s=new qt(e),r,n;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(s.stack=ji.create(),r=this._leakageMon.check(s.stack,this._size+1)),js&&(s.stack=n??ji.create()),this._listeners?this._listeners instanceof qt?(this._deliveryQueue??(this._deliveryQueue=new Vo),this._listeners=[this._listeners,s]):this._listeners.push(s):(this._options?.onWillAddFirstListener?.(this),this._listeners=s,this._options?.onDidAddFirstListener?.(this)),this._size++;let o=je(()=>{Vt?.unregister(o),r?.(),this._removeListener(s)});if(i instanceof tt?i.add(o):Array.isArray(i)&&i.push(o),Vt){let h=new Error().stack.split(` `).slice(2,3).join(` -`).trim(),l=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(h);Ut.register(o,l?.[2]??h,o)}return o}),this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(this._size===1){this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),this._size=0;return}let t=this._listeners,i=t.indexOf(e);if(i===-1)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,t[i]=void 0;let s=this._deliveryQueue.current===this;if(this._size*zo<=t.length){let r=0;for(let n=0;n0}},$o=class{constructor(){this.i=-1,this.end=0}enqueue(e,t,i){this.i=0,this.end=i,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},Yi=class{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new se,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new se,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}getZoomLevel(t){return this.mapWindowIdToZoomLevel.get(this.getWindowId(t))??0}setZoomLevel(t,i){if(this.getZoomLevel(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToZoomLevel.set(s,t),this._onDidChangeZoomLevel.fire(s)}getZoomFactor(t){return this.mapWindowIdToZoomFactor.get(this.getWindowId(t))??1}setZoomFactor(t,i){this.mapWindowIdToZoomFactor.set(this.getWindowId(i),t)}setFullscreen(t,i){if(this.isFullscreen(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToFullScreen.set(s,t),this._onDidChangeFullscreen.fire(s)}isFullscreen(t){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(t))}getWindowId(t){return t.vscodeWindowId}};Yi.INSTANCE=new Yi;var ks=Yi;function Uo(e,t,i){typeof t=="string"&&(t=e.matchMedia(t)),t.addEventListener("change",i)}var $l=ks.INSTANCE.onDidChangeZoomLevel;function qo(e){return ks.INSTANCE.getZoomFactor(e)}var Ul=ks.INSTANCE.onDidChangeFullscreen,lt=typeof navigator=="object"?navigator.userAgent:"",ji=lt.indexOf("Firefox")>=0,qt=lt.indexOf("AppleWebKit")>=0,xs=lt.indexOf("Chrome")>=0,un=!xs&<.indexOf("Safari")>=0,ql=lt.indexOf("Electron/")>=0,Vl=lt.indexOf("Android")>=0,Vt=!1;if(typeof me.matchMedia=="function"){let e=me.matchMedia("(display-mode: standalone) or (display-mode: window-controls-overlay)"),t=me.matchMedia("(display-mode: fullscreen)");Vt=e.matches,Uo(me,e,({matches:i})=>{Vt&&t.matches||(Vt=i)})}function Vo(){return Vt}var nt="en",ti=!1,ii=!1,Et=!1,Yo=!1,_n=!1,fn=!1,jo=!1,Xo=!1,Go=!1,Jo=!1,Nt,Yt=nt,js=nt,Zo,Te,Oe=globalThis,ve;typeof Oe.vscode<"u"&&typeof Oe.vscode.process<"u"?ve=Oe.vscode.process:typeof process<"u"&&typeof process?.versions?.node=="string"&&(ve=process);var pn=typeof ve?.versions?.electron=="string",Qo=pn&&ve?.type==="renderer";if(typeof ve=="object"){ti=ve.platform==="win32",ii=ve.platform==="darwin",Et=ve.platform==="linux",Yo=Et&&!!ve.env.SNAP&&!!ve.env.SNAP_REVISION,jo=pn,Go=!!ve.env.CI||!!ve.env.BUILD_ARTIFACTSTAGINGDIRECTORY,Nt=nt,Yt=nt;let e=ve.env.VSCODE_NLS_CONFIG;if(e)try{let t=JSON.parse(e);Nt=t.userLocale,js=t.osLocale,Yt=t.resolvedLanguage||nt,Zo=t.languagePack?.translationsConfigFile}catch{}_n=!0}else typeof navigator=="object"&&!Qo?(Te=navigator.userAgent,ti=Te.indexOf("Windows")>=0,ii=Te.indexOf("Macintosh")>=0,Xo=(Te.indexOf("Macintosh")>=0||Te.indexOf("iPad")>=0||Te.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,Et=Te.indexOf("Linux")>=0,Jo=Te?.indexOf("Mobi")>=0,fn=!0,Yt=globalThis._VSCODE_NLS_LANGUAGE||nt,Nt=navigator.language.toLowerCase(),js=Nt):console.error("Unable to resolve platform.");var gi=0;ii?gi=1:ti?gi=3:Et&&(gi=2);var gn=ti,Re=ii,ea=Et,vi=_n,ta=fn&&typeof Oe.importScripts=="function",Yl=ta?Oe.origin:void 0,Me=Te,Ke=Yt,ia;(e=>{function t(){return Ke}e.value=t;function i(){return Ke.length===2?Ke==="en":Ke.length>=3?Ke[0]==="e"&&Ke[1]==="n"&&Ke[2]==="-":!1}e.isDefaultVariant=i;function s(){return Ke==="en"}e.isDefault=s})(ia||(ia={}));var sa=typeof Oe.postMessage=="function"&&!Oe.importScripts,ra=(()=>{if(sa){let e=[];Oe.addEventListener("message",i=>{if(i.data&&i.data.vscodeScheduleAsyncWork)for(let s=0,r=e.length;s{let s=++t;e.push({id:s,callback:i}),Oe.postMessage({vscodeScheduleAsyncWork:s},"*")}}return e=>setTimeout(e)})(),na=!!(Me&&Me.indexOf("Chrome")>=0),jl=!!(Me&&Me.indexOf("Firefox")>=0),Xl=!!(!na&&Me&&Me.indexOf("Safari")>=0),Gl=!!(Me&&Me.indexOf("Edg/")>=0),Jl=!!(Me&&Me.indexOf("Android")>=0),$e=typeof navigator=="object"?navigator:{},Zl={clipboard:{writeText:vi||document.queryCommandSupported&&document.queryCommandSupported("copy")||!!($e&&$e.clipboard&&$e.clipboard.writeText),readText:vi||!!($e&&$e.clipboard&&$e.clipboard.readText)},keyboard:vi||Vo()?0:$e.keyboard||un?1:2,touch:"ontouchstart"in me||$e.maxTouchPoints>0,pointerEvents:me.PointerEvent&&("ontouchstart"in me||navigator.maxTouchPoints>0)},Es=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||0}},mi=new Es,Xs=new Es,Gs=new Es,oa=new Array(230),vn;(e=>{function t(h){return mi.keyCodeToStr(h)}e.toString=t;function i(h){return mi.strToKeyCode(h)}e.fromString=i;function s(h){return Xs.keyCodeToStr(h)}e.toUserSettingsUS=s;function r(h){return Gs.keyCodeToStr(h)}e.toUserSettingsGeneral=r;function n(h){return Xs.strToKeyCode(h)||Gs.strToKeyCode(h)}e.fromUserSettings=n;function o(h){if(h>=98&&h<=113)return null;switch(h){case 16:return"Up";case 18:return"Down";case 15:return"Left";case 17:return"Right"}return mi.keyCodeToStr(h)}e.toElectronAccelerator=o})(vn||(vn={}));var aa=class mn{constructor(t,i,s,r,n){this.ctrlKey=t,this.shiftKey=i,this.altKey=s,this.metaKey=r,this.keyCode=n}equals(t){return t instanceof mn&&this.ctrlKey===t.ctrlKey&&this.shiftKey===t.shiftKey&&this.altKey===t.altKey&&this.metaKey===t.metaKey&&this.keyCode===t.keyCode}getHashCode(){let t=this.ctrlKey?"1":"0",i=this.shiftKey?"1":"0",s=this.altKey?"1":"0",r=this.metaKey?"1":"0";return`K${t}${i}${s}${r}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new ha([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}},ha=class{constructor(e){if(e.length===0)throw So("chords");this.chords=e}getHashCode(){let e="";for(let t=0,i=this.chords.length;t{function t(i){return i===e.None||i===e.Cancelled||i instanceof va?!0:!i||typeof i!="object"?!1:typeof i.isCancellationRequested=="boolean"&&typeof i.onCancellationRequested=="function"}e.isCancellationToken=t,e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:li.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:Sn})})(ga||(ga={}));var va=class{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?Sn:(this._emitter||(this._emitter=new se),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}};var Bs=class{constructor(e,t){this._isDisposed=!1,this._token=-1,typeof e=="function"&&typeof t=="number"&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new Hi("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new Hi("Calling 'setIfNotSet' on a disposed TimeoutTimer");this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},ma=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this.isDisposed)throw new Hi("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();let s=i.setInterval(()=>{e()},t);this.disposable=Ye(()=>{i.clearInterval(s),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}},Sa,Si;(function(){typeof globalThis.requestIdleCallback!="function"||typeof globalThis.cancelIdleCallback!="function"?Si=(e,t)=>{ra(()=>{if(i)return;let s=Date.now()+15;t(Object.freeze({didTimeout:!0,timeRemaining(){return Math.max(0,s-Date.now())}}))});let i=!1;return{dispose(){i||(i=!0)}}}:Si=(e,t,i)=>{let s=e.requestIdleCallback(t,typeof i=="number"?{timeout:i}:void 0),r=!1;return{dispose(){r||(r=!0,e.cancelIdleCallback(s))}}},Sa=e=>Si(globalThis,e)})();var wa;(e=>{async function t(s){let r,n=await Promise.all(s.map(o=>o.then(h=>h,h=>{r||(r=h)})));if(typeof r<"u")throw r;return n}e.settled=t;function i(s){return new Promise(async(r,n)=>{try{await s(r,n)}catch(o){n(o)}})}e.withAsyncBody=i})(wa||(wa={}));var Qs=class we{static fromArray(t){return new we(i=>{i.emitMany(t)})}static fromPromise(t){return new we(async i=>{i.emitMany(await t)})}static fromPromises(t){return new we(async i=>{await Promise.all(t.map(async s=>i.emitOne(await s)))})}static merge(t){return new we(async i=>{await Promise.all(t.map(async s=>{for await(let r of s)i.emitOne(r)}))})}constructor(t,i){this._state=0,this._results=[],this._error=null,this._onReturn=i,this._onStateChanged=new se,queueMicrotask(async()=>{let s={emitOne:r=>this.emitOne(r),emitMany:r=>this.emitMany(r),reject:r=>this.reject(r)};try{await Promise.resolve(t(s)),this.resolve()}catch(r){this.reject(r)}finally{s.emitOne=void 0,s.emitMany=void 0,s.reject=void 0}})}[Symbol.asyncIterator](){let t=0;return{next:async()=>{do{if(this._state===2)throw this._error;if(t(this._onReturn?.(),{done:!0,value:void 0})}}static map(t,i){return new we(async s=>{for await(let r of t)s.emitOne(i(r))})}map(t){return we.map(this,t)}static filter(t,i){return new we(async s=>{for await(let r of t)i(r)&&s.emitOne(r)})}filter(t){return we.filter(this,t)}static coalesce(t){return we.filter(t,i=>!!i)}coalesce(){return we.coalesce(this)}static async toPromise(t){let i=[];for await(let s of t)i.push(s);return i}toPromise(){return we.toPromise(this)}emitOne(t){this._state===0&&(this._results.push(t),this._onStateChanged.fire())}emitMany(t){this._state===0&&(this._results=this._results.concat(t),this._onStateChanged.fire())}resolve(){this._state===0&&(this._state=1,this._onStateChanged.fire())}reject(t){this._state===0&&(this._state=2,this._error=t,this._onStateChanged.fire())}};Qs.EMPTY=Qs.fromArray([]);function ba(e){return 55296<=e&&e<=56319}function er(e){return 56320<=e&&e<=57343}function ya(e,t){return(e-55296<<10)+(t-56320)+65536}function Ca(e){return Ds(e,0)}function Ds(e,t){switch(typeof e){case"object":return e===null?Ae(349,t):Array.isArray(e)?xa(e,t):Ea(e,t);case"string":return wn(e,t);case"boolean":return ka(e,t);case"number":return Ae(e,t);case"undefined":return Ae(937,t);default:return Ae(617,t)}}function Ae(e,t){return(t<<5)-t+e|0}function ka(e,t){return Ae(e?433:863,t)}function wn(e,t){t=Ae(149417,t);for(let i=0,s=e.length;iDs(s,i),t)}function Ea(e,t){return t=Ae(181387,t),Object.keys(e).sort().reduce((i,s)=>(i=wn(s,i),Ds(e[s],i)),t)}function wi(e,t,i=32){let s=i-t,r=~((1<>>s)>>>0}function tr(e,t=0,i=e.byteLength,s=0){for(let r=0;ri.toString(16).padStart(2,"0")).join(""):Ba((e>>>0).toString(16),t/4)}var Da=class bn{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(67),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(t){let i=t.length;if(i===0)return;let s=this._buff,r=this._buffLen,n=this._leftoverHighSurrogate,o,h;for(n!==0?(o=n,h=-1,n=0):(o=t.charCodeAt(0),h=0);;){let l=o;if(ba(o))if(h+1>>6,t[i++]=128|(s&63)>>>0):s<65536?(t[i++]=224|(s&61440)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0):(t[i++]=240|(s&1835008)>>>18,t[i++]=128|(s&258048)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0),i>=64&&(this._step(),i-=64,this._totalLen+=64,t[0]=t[64],t[1]=t[65],t[2]=t[66]),i}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),_t(this._h0)+_t(this._h1)+_t(this._h2)+_t(this._h3)+_t(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,tr(this._buff,this._buffLen),this._buffLen>56&&(this._step(),tr(this._buff));let t=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(t/4294967296),!1),this._buffDV.setUint32(60,t%4294967296,!1),this._step()}_step(){let t=bn._bigBlock32,i=this._buffDV;for(let c=0;c<64;c+=4)t.setUint32(c,i.getUint32(c,!1),!1);for(let c=64;c<320;c+=4)t.setUint32(c,wi(t.getUint32(c-12,!1)^t.getUint32(c-32,!1)^t.getUint32(c-56,!1)^t.getUint32(c-64,!1),1),!1);let s=this._h0,r=this._h1,n=this._h2,o=this._h3,h=this._h4,l,a,d;for(let c=0;c<80;c++)c<20?(l=r&n|~r&o,a=1518500249):c<40?(l=r^n^o,a=1859775393):c<60?(l=r&n|r&o|n&o,a=2400959708):(l=r^n^o,a=3395469782),d=wi(s,5)+l+h+a+t.getUint32(c*4,!1)&4294967295,h=o,o=n,n=wi(r,30),r=s,s=d;this._h0=this._h0+s&4294967295,this._h1=this._h1+r&4294967295,this._h2=this._h2+n&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+h&4294967295}};Da._bigBlock32=new DataView(new ArrayBuffer(320));var{registerWindow:Ql,getWindow:be,getDocument:ec,getWindows:tc,getWindowsCount:ic,getWindowId:ir,getWindowById:sc,hasWindow:rc,onDidRegisterWindow:Ra,onWillUnregisterWindow:nc,onDidUnregisterWindow:oc}=(function(){let e=new Map,t={window:me,disposables:new et};e.set(me.vscodeWindowId,t);let i=new se,s=new se,r=new se;function n(o,h){return(typeof o=="number"?e.get(o):void 0)??(h?t:void 0)}return{onDidRegisterWindow:i.event,onWillUnregisterWindow:r.event,onDidUnregisterWindow:s.event,registerWindow(o){if(e.has(o.vscodeWindowId))return Se.None;let h=new et,l={window:o,disposables:h.add(new et)};return e.set(o.vscodeWindowId,l),h.add(Ye(()=>{e.delete(o.vscodeWindowId),s.fire(o)})),h.add(B(o,Z.BEFORE_UNLOAD,()=>{r.fire(o)})),i.fire(l),h},getWindows(){return e.values()},getWindowsCount(){return e.size},getWindowId(o){return o.vscodeWindowId},hasWindow(o){return e.has(o)},getWindowById:n,getWindow(o){let h=o;if(h?.ownerDocument?.defaultView)return h.ownerDocument.defaultView.window;let l=o;return l?.view?l.view.window:me},getDocument(o){return be(o).document}}})(),Ma=class{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}};function B(e,t,i,s){return new Ma(e,t,i,s)}function La(e,t){return function(i){return t(new wt(e,i))}}function Pa(e){return function(t){return e(new Xi(t))}}var sr=function(e,t,i,s){let r=i;return t==="click"||t==="mousedown"||t==="contextmenu"?r=La(be(e),i):(t==="keydown"||t==="keypress"||t==="keyup")&&(r=Pa(i)),B(e,t,r,s)},Ta,si,Aa=class extends ma{constructor(e){super(),this.defaultTarget=e&&be(e)}cancelAndSet(e,t,i){return super.cancelAndSet(e,t,i??this.defaultTarget)}},bi=class{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){Kt(e)}}static sort(e,t){return t.priority-e.priority}};(function(){let e=new Map,t=new Map,i=new Map,s=new Map,r=n=>{i.set(n,!1);let o=e.get(n)??[];for(t.set(n,o),e.set(n,[]),s.set(n,!0);o.length>0;)o.sort(bi.sort),o.shift().execute();s.set(n,!1)};si=(n,o,h=0)=>{let l=ir(n),a=new bi(o,h),d=e.get(l);return d||(d=[],e.set(l,d)),d.push(a),i.get(l)||(i.set(l,!0),n.requestAnimationFrame(()=>r(l))),a},Ta=(n,o,h)=>{let l=ir(n);if(s.get(l)){let a=new bi(o,h),d=t.get(l);return d||(d=[],t.set(l,d)),d.push(a),a}else return si(n,o,h)}})();var rr=class jt{constructor(t,i){this.width=t,this.height=i}with(t=this.width,i=this.height){return t!==this.width||i!==this.height?new jt(t,i):this}static is(t){return typeof t=="object"&&typeof t.height=="number"&&typeof t.width=="number"}static lift(t){return t instanceof jt?t:new jt(t.width,t.height)}static equals(t,i){return t===i?!0:!t||!i?!1:t.width===i.width&&t.height===i.height}};rr.None=new rr(0,0);function Oa(e){let t=e.getBoundingClientRect(),i=be(e);return{left:t.left+i.scrollX,top:t.top+i.scrollY,width:t.width,height:t.height}}var ac=new class{constructor(){this.mutationObservers=new Map}observe(e,t,i){let s=this.mutationObservers.get(e);s||(s=new Map,this.mutationObservers.set(e,s));let r=Ca(i),n=s.get(r);if(n)n.users+=1;else{let o=new se,h=new MutationObserver(a=>o.fire(a));h.observe(e,i);let l=n={users:1,observer:h,onDidMutate:o.event};t.add(Ye(()=>{l.users-=1,l.users===0&&(o.dispose(),h.disconnect(),s?.delete(r),s?.size===0&&this.mutationObservers.delete(e))})),s.set(r,n)}return n.onDidMutate}},Z={CLICK:"click",AUXCLICK:"auxclick",DBLCLICK:"dblclick",MOUSE_UP:"mouseup",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_MOVE:"mousemove",MOUSE_OUT:"mouseout",MOUSE_ENTER:"mouseenter",MOUSE_LEAVE:"mouseleave",MOUSE_WHEEL:"wheel",POINTER_UP:"pointerup",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_LEAVE:"pointerleave",CONTEXT_MENU:"contextmenu",WHEEL:"wheel",KEY_DOWN:"keydown",KEY_PRESS:"keypress",KEY_UP:"keyup",LOAD:"load",BEFORE_UNLOAD:"beforeunload",UNLOAD:"unload",PAGE_SHOW:"pageshow",PAGE_HIDE:"pagehide",PASTE:"paste",ABORT:"abort",ERROR:"error",RESIZE:"resize",SCROLL:"scroll",FULLSCREEN_CHANGE:"fullscreenchange",WK_FULLSCREEN_CHANGE:"webkitfullscreenchange",SELECT:"select",CHANGE:"change",SUBMIT:"submit",RESET:"reset",FOCUS:"focus",FOCUS_IN:"focusin",FOCUS_OUT:"focusout",BLUR:"blur",INPUT:"input",STORAGE:"storage",DRAG_START:"dragstart",DRAG:"drag",DRAG_ENTER:"dragenter",DRAG_LEAVE:"dragleave",DRAG_OVER:"dragover",DROP:"drop",DRAG_END:"dragend",ANIMATION_START:qt?"webkitAnimationStart":"animationstart",ANIMATION_END:qt?"webkitAnimationEnd":"animationend",ANIMATION_ITERATION:qt?"webkitAnimationIteration":"animationiteration"},Ia=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function yn(e,t,i,...s){let r=Ia.exec(t);if(!r)throw new Error("Bad use of emmet");let n=r[1]||"div",o;return e!=="http://www.w3.org/1999/xhtml"?o=document.createElementNS(e,n):o=document.createElement(n),r[3]&&(o.id=r[3]),r[4]&&(o.className=r[4].replace(/\./g," ").trim()),i&&Object.entries(i).forEach(([h,l])=>{typeof l>"u"||(/^on\w+$/.test(h)?o[h]=l:h==="selected"?l&&o.setAttribute(h,"true"):o.setAttribute(h,l))}),o.append(...s),o}function Na(e,t,...i){return yn("http://www.w3.org/1999/xhtml",e,t,...i)}Na.SVG=function(e,t,...i){return yn("http://www.w3.org/2000/svg",e,t,...i)};var Fa=class{constructor(e){this.domNode=e,this._maxWidth="",this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._paddingTop="",this._paddingLeft="",this._paddingBottom="",this._paddingRight="",this._fontFamily="",this._fontWeight="",this._fontSize="",this._fontStyle="",this._fontFeatureSettings="",this._fontVariationSettings="",this._textDecoration="",this._lineHeight="",this._letterSpacing="",this._className="",this._display="",this._position="",this._visibility="",this._color="",this._backgroundColor="",this._layerHint=!1,this._contain="none",this._boxShadow=""}setMaxWidth(e){let t=de(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){let t=de(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){let t=de(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){let t=de(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){let t=de(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){let t=de(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){let t=de(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){let t=de(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){let t=de(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){let t=de(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){let t=de(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){let t=de(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){let t=de(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){let t=de(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?"translate3d(0px, 0px, 0px)":"")}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}};function de(e){return typeof e=="number"?`${e}px`:e}function Bt(e){return new Fa(e)}var Cn=class{constructor(){this._hooks=new et,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let i=this._onStopCallback;this._onStopCallback=null,e&&i&&i(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,i,s,r){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=s,this._onStopCallback=r;let n=e;try{e.setPointerCapture(t),this._hooks.add(Ye(()=>{try{e.releasePointerCapture(t)}catch{}}))}catch{n=be(e)}this._hooks.add(B(n,Z.POINTER_MOVE,o=>{if(o.buttons!==i){this.stopMonitoring(!0);return}o.preventDefault(),this._pointerMoveCallback(o)})),this._hooks.add(B(n,Z.POINTER_UP,o=>this.stopMonitoring(!0)))}};function Ha(e,t,i){let s=null,r=null;if(typeof i.value=="function"?(s="value",r=i.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof i.get=="function"&&(s="get",r=i.get),!r)throw new Error("not supported");let n=`$memoize$${t}`;i[s]=function(...o){return this.hasOwnProperty(n)||Object.defineProperty(this,n,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,o)}),this[n]}}var Ce;(e=>(e.Tap="-xterm-gesturetap",e.Change="-xterm-gesturechange",e.Start="-xterm-gesturestart",e.End="-xterm-gesturesend",e.Contextmenu="-xterm-gesturecontextmenu"))(Ce||(Ce={}));var bt=class ae extends Se{constructor(){super(),this.dispatched=!1,this.targets=new qs,this.ignoreTargets=new qs,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(li.runAndSubscribe(Ra,({window:t,disposables:i})=>{i.add(B(t.document,"touchstart",s=>this.onTouchStart(s),{passive:!1})),i.add(B(t.document,"touchend",s=>this.onTouchEnd(t,s))),i.add(B(t.document,"touchmove",s=>this.onTouchMove(s),{passive:!1}))},{window:me,disposables:this._store}))}static addTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Us(new ae));let i=ae.INSTANCE.targets.push(t);return Ye(i)}static ignoreTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Us(new ae));let i=ae.INSTANCE.ignoreTargets.push(t);return Ye(i)}static isTouchDevice(){return"ontouchstart"in me||navigator.maxTouchPoints>0}dispose(){this.handle&&(this.handle.dispose(),this.handle=null),super.dispose()}onTouchStart(t){let i=Date.now();this.handle&&(this.handle.dispose(),this.handle=null);for(let s=0,r=t.targetTouches.length;s=ae.HOLD_DELAY&&Math.abs(l.initialPageX-fe(l.rollingPageX))<30&&Math.abs(l.initialPageY-fe(l.rollingPageY))<30){let d=this.newGestureEvent(Ce.Contextmenu,l.initialTarget);d.pageX=fe(l.rollingPageX),d.pageY=fe(l.rollingPageY),this.dispatchEvent(d)}else if(r===1){let d=fe(l.rollingPageX),c=fe(l.rollingPageY),u=fe(l.rollingTimestamps)-l.rollingTimestamps[0],f=d-l.rollingPageX[0],_=c-l.rollingPageY[0],p=[...this.targets].filter(y=>l.initialTarget instanceof Node&&y.contains(l.initialTarget));this.inertia(t,p,s,Math.abs(f)/u,f>0?1:-1,d,Math.abs(_)/u,_>0?1:-1,c)}this.dispatchEvent(this.newGestureEvent(Ce.End,l.initialTarget)),delete this.activeTouches[h.identifier]}this.dispatched&&(i.preventDefault(),i.stopPropagation(),this.dispatched=!1)}newGestureEvent(t,i){let s=document.createEvent("CustomEvent");return s.initEvent(t,!1,!0),s.initialTarget=i,s.tapCount=0,s}dispatchEvent(t){if(t.type===Ce.Tap){let i=new Date().getTime(),s=0;i-this._lastSetTapCountTime>ae.CLEAR_TAP_COUNT_TIME?s=1:s=2,this._lastSetTapCountTime=i,t.tapCount=s}else(t.type===Ce.Change||t.type===Ce.Contextmenu)&&(this._lastSetTapCountTime=0);if(t.initialTarget instanceof Node){for(let s of this.ignoreTargets)if(s.contains(t.initialTarget))return;let i=[];for(let s of this.targets)if(s.contains(t.initialTarget)){let r=0,n=t.initialTarget;for(;n&&n!==s;)r++,n=n.parentElement;i.push([r,s])}i.sort((s,r)=>s[0]-r[0]);for(let[s,r]of i)r.dispatchEvent(t),this.dispatched=!0}}inertia(t,i,s,r,n,o,h,l,a){this.handle=si(t,()=>{let d=Date.now(),c=d-s,u=0,f=0,_=!0;r+=ae.SCROLL_FRICTION*c,h+=ae.SCROLL_FRICTION*c,r>0&&(_=!1,u=n*r*c),h>0&&(_=!1,f=l*h*c);let p=this.newGestureEvent(Ce.Change);p.translationX=u,p.translationY=f,i.forEach(y=>y.dispatchEvent(p)),_||this.inertia(t,i,d,r,n,o+u,h,l,a+f)})}onTouchMove(t){let i=Date.now();for(let s=0,r=t.changedTouches.length;s3&&(o.rollingPageX.shift(),o.rollingPageY.shift(),o.rollingTimestamps.shift()),o.rollingPageX.push(n.pageX),o.rollingPageY.push(n.pageY),o.rollingTimestamps.push(i)}this.dispatched&&(t.preventDefault(),t.stopPropagation(),this.dispatched=!1)}};bt.SCROLL_FRICTION=-.005,bt.HOLD_DELAY=700,bt.CLEAR_TAP_COUNT_TIME=400,K([Ha],bt,"isTouchDevice",1);var kn=bt,Rs=class extends Se{onclick(e,t){this._register(B(e,Z.CLICK,i=>t(new wt(be(e),i))))}onmousedown(e,t){this._register(B(e,Z.MOUSE_DOWN,i=>t(new wt(be(e),i))))}onmouseover(e,t){this._register(B(e,Z.MOUSE_OVER,i=>t(new wt(be(e),i))))}onmouseleave(e,t){this._register(B(e,Z.MOUSE_LEAVE,i=>t(new wt(be(e),i))))}onkeydown(e,t){this._register(B(e,Z.KEY_DOWN,i=>t(new Xi(i))))}onkeyup(e,t){this._register(B(e,Z.KEY_UP,i=>t(new Xi(i))))}oninput(e,t){this._register(B(e,Z.INPUT,t))}onblur(e,t){this._register(B(e,Z.BLUR,t))}onfocus(e,t){this._register(B(e,Z.FOCUS,t))}onchange(e,t){this._register(B(e,Z.CHANGE,t))}ignoreGesture(e){return kn.ignoreTarget(e)}},nr=11,Wa=class extends Rs{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=e.bgWidth+"px",this.bgDomNode.style.height=e.bgHeight+"px",typeof e.top<"u"&&(this.bgDomNode.style.top="0px"),typeof e.left<"u"&&(this.bgDomNode.style.left="0px"),typeof e.bottom<"u"&&(this.bgDomNode.style.bottom="0px"),typeof e.right<"u"&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=e.className,this.domNode.style.position="absolute",this.domNode.style.width=nr+"px",this.domNode.style.height=nr+"px",typeof e.top<"u"&&(this.domNode.style.top=e.top+"px"),typeof e.left<"u"&&(this.domNode.style.left=e.left+"px"),typeof e.bottom<"u"&&(this.domNode.style.bottom=e.bottom+"px"),typeof e.right<"u"&&(this.domNode.style.right=e.right+"px"),this._pointerMoveMonitor=this._register(new Cn),this._register(sr(this.bgDomNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._register(sr(this.domNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._pointerdownRepeatTimer=this._register(new Aa),this._pointerdownScheduleRepeatTimer=this._register(new Bs)}_arrowPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,be(e))};this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(t,200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,i=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault()}},za=class Gi{constructor(t,i,s,r,n,o,h){this._forceIntegerValues=t,this._scrollStateBrand=void 0,this._forceIntegerValues&&(i=i|0,s=s|0,r=r|0,n=n|0,o=o|0,h=h|0),this.rawScrollLeft=r,this.rawScrollTop=h,i<0&&(i=0),r+i>s&&(r=s-i),r<0&&(r=0),n<0&&(n=0),h+n>o&&(h=o-n),h<0&&(h=0),this.width=i,this.scrollWidth=s,this.scrollLeft=r,this.height=n,this.scrollHeight=o,this.scrollTop=h}equals(t){return this.rawScrollLeft===t.rawScrollLeft&&this.rawScrollTop===t.rawScrollTop&&this.width===t.width&&this.scrollWidth===t.scrollWidth&&this.scrollLeft===t.scrollLeft&&this.height===t.height&&this.scrollHeight===t.scrollHeight&&this.scrollTop===t.scrollTop}withScrollDimensions(t,i){return new Gi(this._forceIntegerValues,typeof t.width<"u"?t.width:this.width,typeof t.scrollWidth<"u"?t.scrollWidth:this.scrollWidth,i?this.rawScrollLeft:this.scrollLeft,typeof t.height<"u"?t.height:this.height,typeof t.scrollHeight<"u"?t.scrollHeight:this.scrollHeight,i?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new Gi(this._forceIntegerValues,this.width,this.scrollWidth,typeof t.scrollLeft<"u"?t.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof t.scrollTop<"u"?t.scrollTop:this.rawScrollTop)}createScrollEvent(t,i){let s=this.width!==t.width,r=this.scrollWidth!==t.scrollWidth,n=this.scrollLeft!==t.scrollLeft,o=this.height!==t.height,h=this.scrollHeight!==t.scrollHeight,l=this.scrollTop!==t.scrollTop;return{inSmoothScrolling:i,oldWidth:t.width,oldScrollWidth:t.scrollWidth,oldScrollLeft:t.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:t.height,oldScrollHeight:t.scrollHeight,oldScrollTop:t.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:s,scrollWidthChanged:r,scrollLeftChanged:n,heightChanged:o,scrollHeightChanged:h,scrollTopChanged:l}}},Ka=class extends Se{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new za(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let i=this._state.withScrollDimensions(e,t);this._setState(i,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>"u"?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>"u"?this._smoothScrolling.to.scrollTop:e.scrollTop};let i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;t?s=new ar(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):s=this._smoothScrolling.combine(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{let i=this._state.withScrollPosition(e);this._smoothScrolling=ar.start(this._state,i,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),!!this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}},or=class{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}};function yi(e,t){let i=t-e;return function(s){return e+i*qa(s)}}function $a(e,t,i){return function(s){return s2.5*s){let r,n;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" fade":"")))}},Ya=140,xn=class extends Rs{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new Va(e.visibility,"visible scrollbar "+e.extraScrollbarClassName,"invisible scrollbar "+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new Cn),this._shouldRender=!0,this.domNode=Bt(document.createElement("div")),this.domNode.setAttribute("role","presentation"),this.domNode.setAttribute("aria-hidden","true"),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition("absolute"),this._register(B(this.domNode.domNode,Z.POINTER_DOWN,t=>this._domNodePointerDown(t)))}_createArrow(e){let t=this._register(new Wa(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,i,s){this.slider=Bt(document.createElement("div")),this.slider.setClassName("slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),typeof i=="number"&&this.slider.setWidth(i),typeof s=="number"&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(B(this.slider.domNode,Z.POINTER_DOWN,r=>{r.button===0&&(r.preventDefault(),this._sliderPointerDown(r))})),this.onclick(this.slider.domNode,r=>{r.leftButton&&r.stopPropagation()})}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){let t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?e.button===0&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,i;if(e.target===this.domNode.domNode&&typeof e.offsetX=="number"&&typeof e.offsetY=="number")t=e.offsetX,i=e.offsetY;else{let r=Oa(this.domNode.domNode);t=e.pageX-r.left,i=e.pageY-r.top}let s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,r=>{let n=this._sliderOrthogonalPointerPosition(r),o=Math.abs(n-i);if(gn&&o>Ya){this._setDesiredScrollPositionNow(s.getScrollPosition());return}let h=this._sliderPointerPosition(r)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(h))},()=>{this.slider.toggleClassName("active",!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(e){let t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}},En=class Zi{constructor(t,i,s,r,n,o){this._scrollbarSize=Math.round(i),this._oppositeScrollbarSize=Math.round(s),this._arrowSize=Math.round(t),this._visibleSize=r,this._scrollSize=n,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new Zi(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(t){let i=Math.round(t);return this._visibleSize!==i?(this._visibleSize=i,this._refreshComputedValues(),!0):!1}setScrollSize(t){let i=Math.round(t);return this._scrollSize!==i?(this._scrollSize=i,this._refreshComputedValues(),!0):!1}setScrollPosition(t){let i=Math.round(t);return this._scrollPosition!==i?(this._scrollPosition=i,this._refreshComputedValues(),!0):!1}setScrollbarSize(t){this._scrollbarSize=Math.round(t)}setOppositeScrollbarSize(t){this._oppositeScrollbarSize=Math.round(t)}static _computeValues(t,i,s,r,n){let o=Math.max(0,s-t),h=Math.max(0,o-2*i),l=r>0&&r>s;if(!l)return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(h),computedSliderRatio:0,computedSliderPosition:0};let a=Math.round(Math.max(20,Math.floor(s*h/r))),d=(h-a)/(r-s),c=n*d;return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(a),computedSliderRatio:d,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){let t=Zi._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=t.computedAvailableSize,this._computedIsNeeded=t.computedIsNeeded,this._computedSliderSize=t.computedSliderSize,this._computedSliderRatio=t.computedSliderRatio,this._computedSliderPosition=t.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize-this._computedSliderSize/2;return Math.round(i/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize,s=this._scrollPosition;return i0&&Math.abs(t.deltaY)>0)return 1;let s=.5;if((!this._isAlmostInt(t.deltaX)||!this._isAlmostInt(t.deltaY))&&(s+=.25),i){let r=Math.abs(t.deltaX),n=Math.abs(t.deltaY),o=Math.abs(i.deltaX),h=Math.abs(i.deltaY),l=Math.max(Math.min(r,o),1),a=Math.max(Math.min(n,h),1),d=Math.max(r,o),c=Math.max(n,h);d%l===0&&c%a===0&&(s-=.5)}return Math.min(Math.max(s,0),1)}_isAlmostInt(t){return Math.abs(Math.round(t)-t)<.01}};Qi.INSTANCE=new Qi;var Za=Qi,Qa=class extends Rs{constructor(e,t,i){super(),this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new se),this.onWillScroll=this._onWillScroll.event,this._options=th(t),this._scrollable=i,this._register(this._scrollable.onScroll(r=>{this._onWillScroll.fire(r),this._onDidScroll(r),this._onScroll.fire(r)}));let s={onMouseWheel:r=>this._onMouseWheel(r),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new Xa(this._scrollable,this._options,s)),this._horizontalScrollbar=this._register(new ja(this._scrollable,this._options,s)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=Bt(document.createElement("div")),this._leftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=Bt(document.createElement("div")),this._topShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=Bt(document.createElement("div")),this._topLeftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,r=>this._onMouseOver(r)),this.onmouseleave(this._listenOnDomNode,r=>this._onMouseLeave(r)),this._hideTimeout=this._register(new Bs),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=ei(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,Re&&(this._options.className+=" mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){typeof e.handleMouseWheel<"u"&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<"u"&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<"u"&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<"u"&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<"u"&&(this._options.horizontal=e.horizontal),typeof e.vertical<"u"&&(this._options.vertical=e.vertical),typeof e.horizontalScrollbarSize<"u"&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<"u"&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<"u"&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new Zs(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=ei(this._mouseWheelToDispose),e)){let t=i=>{this._onMouseWheel(new Zs(i))};this._mouseWheelToDispose.push(B(this._listenOnDomNode,Z.MOUSE_WHEEL,t,{passive:!1}))}}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=Za.INSTANCE;lr&&t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let r=e.deltaY*this._options.mouseWheelScrollSensitivity,n=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&n+r===0?n=r=0:Math.abs(r)>=Math.abs(n)?n=0:r=0),this._options.flipAxes&&([r,n]=[n,r]);let o=!Re&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||o)&&!n&&(n=r,r=0),e.browserEvent&&e.browserEvent.altKey&&(n=n*this._options.fastScrollSensitivity,r=r*this._options.fastScrollSensitivity);let h=this._scrollable.getFutureScrollPosition(),l={};if(r){let a=hr*r,d=h.scrollTop-(a<0?Math.floor(a):Math.ceil(a));this._verticalScrollbar.writeScrollPosition(l,d)}if(n){let a=hr*n,d=h.scrollLeft-(a<0?Math.floor(a):Math.ceil(a));this._horizontalScrollbar.writeScrollPosition(l,d)}l=this._scrollable.validateScrollPosition(l),(h.scrollLeft!==l.scrollLeft||h.scrollTop!==l.scrollTop)&&(lr&&this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(l):this._scrollable.setScrollPositionNow(l),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" left":"",r=t?" top":"",n=i||t?" top-left-corner":"";this._leftShadowDomNode.setClassName(`shadow${s}`),this._topShadowDomNode.setClassName(`shadow${r}`),this._topLeftShadowDomNode.setClassName(`shadow${n}${r}${s}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),Ga)}},eh=class extends Qa{constructor(e,t,i){super(e,t,i)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function th(e){let t={lazyRender:typeof e.lazyRender<"u"?e.lazyRender:!1,className:typeof e.className<"u"?e.className:"",useShadows:typeof e.useShadows<"u"?e.useShadows:!0,handleMouseWheel:typeof e.handleMouseWheel<"u"?e.handleMouseWheel:!0,flipAxes:typeof e.flipAxes<"u"?e.flipAxes:!1,consumeMouseWheelIfScrollbarIsNeeded:typeof e.consumeMouseWheelIfScrollbarIsNeeded<"u"?e.consumeMouseWheelIfScrollbarIsNeeded:!1,alwaysConsumeMouseWheel:typeof e.alwaysConsumeMouseWheel<"u"?e.alwaysConsumeMouseWheel:!1,scrollYToX:typeof e.scrollYToX<"u"?e.scrollYToX:!1,mouseWheelScrollSensitivity:typeof e.mouseWheelScrollSensitivity<"u"?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof e.fastScrollSensitivity<"u"?e.fastScrollSensitivity:5,scrollPredominantAxis:typeof e.scrollPredominantAxis<"u"?e.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof e.mouseWheelSmoothScroll<"u"?e.mouseWheelSmoothScroll:!0,arrowSize:typeof e.arrowSize<"u"?e.arrowSize:11,listenOnDomNode:typeof e.listenOnDomNode<"u"?e.listenOnDomNode:null,horizontal:typeof e.horizontal<"u"?e.horizontal:1,horizontalScrollbarSize:typeof e.horizontalScrollbarSize<"u"?e.horizontalScrollbarSize:10,horizontalSliderSize:typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:0,horizontalHasArrows:typeof e.horizontalHasArrows<"u"?e.horizontalHasArrows:!1,vertical:typeof e.vertical<"u"?e.vertical:1,verticalScrollbarSize:typeof e.verticalScrollbarSize<"u"?e.verticalScrollbarSize:10,verticalHasArrows:typeof e.verticalHasArrows<"u"?e.verticalHasArrows:!1,verticalSliderSize:typeof e.verticalSliderSize<"u"?e.verticalSliderSize:0,scrollByPage:typeof e.scrollByPage<"u"?e.scrollByPage:!1};return t.horizontalSliderSize=typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof e.verticalSliderSize<"u"?e.verticalSliderSize:t.verticalScrollbarSize,Re&&(t.className+=" mac"),t}var b=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event?this._event:(this._event=(e,t,i)=>{if(this._disposed)return q(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let r=q(()=>{let n=this._listeners.indexOf(s);n!==-1&&this._listeners.splice(n,1)});return i&&(Array.isArray(i)?i.push(r):i.add(r)),r},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:i}=this._listeners[0];t.call(i,e);return}default:{let t=this._listeners.slice();for(let{fn:i,thisArgs:s}of t)i.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},_e;(e=>{function t(n,o){return n(h=>o.fire(h))}e.forward=t;function i(n,o){return(h,l,a)=>n(d=>h.call(l,o(d)),void 0,a)}e.map=i;function s(...n){return(o,h,l)=>{let a=new bs;for(let d of n)a.add(d(c=>o.call(h,c)));return l&&(Array.isArray(l)?l.push(a):l.add(a)),a}}e.any=s;function r(n,o){return o(void 0),n(h=>o(h))}e.runAndSubscribe=r})(_e||(_e={}));var es=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._bufferService=i,this._optionsService=o,this._renderService=h,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;let l=this._register(new Ka({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:a=>si(s.window,a)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{l.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new eh(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},l)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","overviewRuler"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(r.onProtocolChange(a=>{this._scrollableElement.updateOptions({handleMouseWheel:!(a&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{e.style.backgroundColor=n.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=n.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(q(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register(q(()=>this._styleElement.remove())),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .scrollbar > .slider {",` background: ${n.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider:hover {",` background: ${n.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider.active {",` background: ${n.colors.scrollbarSliderActiveBackground.css};`,"}"].join(` -`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._scrollableElement.onScroll(a=>this._handleScroll(a))),this._register(kn.addTarget(t)),this._register(B(t,Ce.Change,a=>this._handleGestureChange(a)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){!this._renderService||this._isSyncing||(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;i!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}_handleGestureChange(e){e.preventDefault(),e.stopPropagation();let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e.translationY})}};es=K([w(2,ce),w(3,Ie),w(4,Xr),w(5,ht),w(6,he),w(7,Ne)],es);var ts=class extends I{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(n=>this._removeDecoration(n))),this._register(q(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,i.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;let i=e.options.x??0;(e.options.anchor||"left")==="right"?t.style.right=i?`${i*this._renderService.dimensions.css.cell.width}px`:"":t.style.left=i?`${i*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};ts=K([w(1,ce),w(2,Ie),w(3,Tt),w(4,Ne)],ts);var ih=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(let t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position)){this._addLineToZone(t,e.marker.line);return}}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}},Be={full:0,left:0,center:0,right:0},Ue={full:0,left:0,center:0,right:0},ft={full:0,left:0,center:0,right:0},ri=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=o,this._coreBrowserService=h,this._colorZoneStore=new ih,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(q(()=>this._canvas?.remove()));let l=this._canvas.getContext("2d");if(l)this._ctx=l;else throw new Error("Ctx cannot be null");this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onRender(()=>{(!this._containerHeight||this._containerHeight!==this._screenElement.clientHeight)&&(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("overviewRuler",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._queueRefresh(!0)}get _width(){return this._optionsService.options.overviewRuler?.width||0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);Ue.full=this._canvas.width,Ue.left=e,Ue.center=t,Ue.right=e,this._refreshDrawHeightConstants(),ft.full=1,ft.left=1,ft.center=1+Ue.left,ft.right=1+Ue.left+Ue.center}_refreshDrawHeightConstants(){Be.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);Be.left=t,Be.center=t,Be.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let t of this._decorationService.decorations)this._colorZoneStore.addDecoration(t);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!=="full"&&this._renderColorZone(t);for(let t of e)t.position==="full"&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(ft[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-Be[e.position||"full"]/2),Ue[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+Be[e.position||"full"]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};ri=K([w(2,ce),w(3,Tt),w(4,Ne),w(5,he),w(6,ht),w(7,Ie)],ri);var is=class{constructor(e,t,i,s,r,n){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=n,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(e.keyCode===20||e.keyCode===229||e.keyCode===16||e.keyCode===17||e.keyCode===18)return!1;this._finalizeComposition(!1)}return e.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){let t={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let i;t.start+=this._dataAlreadySent.length,this._isComposing?i=this._textarea.value.substring(t.start,this._compositionPosition.start):i=this._textarea.value.substring(t.start),i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;let t=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(t,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;let e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){let t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};is=K([w(2,ce),w(3,he),w(4,je),w(5,Ne)],is);var ee=0,te=0,ie=0,$=0,cr={css:"#00000000",rgba:0},V;(e=>{function t(r,n,o,h){return h!==void 0?`#${Ge(r)}${Ge(n)}${Ge(o)}${Ge(h)}`:`#${Ge(r)}${Ge(n)}${Ge(o)}`}e.toCss=t;function i(r,n,o,h=255){return(r<<24|n<<16|o<<8|h)>>>0}e.toRgba=i;function s(r,n,o,h){return{css:e.toCss(r,n,o,h),rgba:e.toRgba(r,n,o,h)}}e.toColor=s})(V||(V={}));var N;(e=>{function t(l,a){if($=(a.rgba&255)/255,$===1)return{css:a.css,rgba:a.rgba};let d=a.rgba>>24&255,c=a.rgba>>16&255,u=a.rgba>>8&255,f=l.rgba>>24&255,_=l.rgba>>16&255,p=l.rgba>>8&255;ee=f+Math.round((d-f)*$),te=_+Math.round((c-_)*$),ie=p+Math.round((u-p)*$);let y=V.toCss(ee,te,ie),E=V.toRgba(ee,te,ie);return{css:y,rgba:E}}e.blend=t;function i(l){return(l.rgba&255)===255}e.isOpaque=i;function s(l,a,d){let c=Xt.ensureContrastRatio(l.rgba,a.rgba,d);if(c)return V.toColor(c>>24&255,c>>16&255,c>>8&255)}e.ensureContrastRatio=s;function r(l){let a=(l.rgba|255)>>>0;return[ee,te,ie]=Xt.toChannels(a),{css:V.toCss(ee,te,ie),rgba:a}}e.opaque=r;function n(l,a){return $=Math.round(a*255),[ee,te,ie]=Xt.toChannels(l.rgba),{css:V.toCss(ee,te,ie,$),rgba:V.toRgba(ee,te,ie,$)}}e.opacity=n;function o(l,a){return $=l.rgba&255,n(l,$*a/255)}e.multiplyOpacity=o;function h(l){return[l.rgba>>24&255,l.rgba>>16&255,l.rgba>>8&255]}e.toColorRGB=h})(N||(N={}));var z;(e=>{let t,i;try{let r=document.createElement("canvas");r.width=1,r.height=1;let n=r.getContext("2d",{willReadFrequently:!0});n&&(t=n,t.globalCompositeOperation="copy",i=t.createLinearGradient(0,0,1,1))}catch{}function s(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),V.toColor(ee,te,ie);case 5:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),$=parseInt(r.slice(4,5).repeat(2),16),V.toColor(ee,te,ie,$);case 7:return{css:r,rgba:(parseInt(r.slice(1),16)<<8|255)>>>0};case 9:return{css:r,rgba:parseInt(r.slice(1),16)>>>0}}let n=r.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(n)return ee=parseInt(n[1]),te=parseInt(n[2]),ie=parseInt(n[3]),$=Math.round((n[5]===void 0?1:parseFloat(n[5]))*255),V.toColor(ee,te,ie,$);if(r==="transparent")return{css:"transparent",rgba:0};if(!t||!i)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=i,t.fillStyle=r,typeof t.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[ee,te,ie,$]=t.getImageData(0,0,1,1).data,$!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:V.toRgba(ee,te,ie,$),css:r}}e.toColor=s})(z||(z={}));var oe;(e=>{function t(s){return i(s>>16&255,s>>8&255,s&255)}e.relativeLuminance=t;function i(s,r,n){let o=s/255,h=r/255,l=n/255,a=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),d=h<=.03928?h/12.92:Math.pow((h+.055)/1.055,2.4),c=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4);return a*.2126+d*.7152+c*.0722}e.relativeLuminance2=i})(oe||(oe={}));var Xt;(e=>{function t(o,h){if($=(h&255)/255,$===1)return h;let l=h>>24&255,a=h>>16&255,d=h>>8&255,c=o>>24&255,u=o>>16&255,f=o>>8&255;return ee=c+Math.round((l-c)*$),te=u+Math.round((a-u)*$),ie=f+Math.round((d-f)*$),V.toRgba(ee,te,ie)}e.blend=t;function i(o,h,l){let a=oe.relativeLuminance(o>>8),d=oe.relativeLuminance(h>>8);if(Pe(a,d)>8));if(_>8));return _>y?f:p}return f}let c=r(o,h,l),u=Pe(a,oe.relativeLuminance(c>>8));if(u>8));return u>_?c:f}return c}}e.ensureContrastRatio=i;function s(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Pe(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p0||f>0||_>0);)u-=Math.max(0,Math.ceil(u*.1)),f-=Math.max(0,Math.ceil(f*.1)),_-=Math.max(0,Math.ceil(_*.1)),p=Pe(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));return(u<<24|f<<16|_<<8|255)>>>0}e.reduceLuminance=s;function r(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Pe(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p>>0}e.increaseLuminance=r;function n(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}e.toChannels=n})(Xt||(Xt={}));function Ge(e){let t=e.toString(16);return t.length<2?"0"+t:t}function Pe(e,t){return e1){let d=this._getJoinedRanges(s,o,n,t,r);for(let c=0;c1){let a=this._getJoinedRanges(s,o,n,t,r);for(let d=0;d=Ot,k=m,v=this._workCell;if(u.length>0&&m===u[0][0]&&S){let T=u.shift(),fi=this._isCellInSelection(T[0],t);for(A=T[0]+1;A=T[1]),S?(C=!0,v=new sh(this._workCell,e.translateToString(!0,T[0],T[1]),T[1]-T[0]),k=T[1]-1,g=v.getWidth()):Ot=T[1]}let M=this._isCellInSelection(m,t),H=i&&m===n,ne=_i&&m>=a&&m<=d,Ee=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{Ee=!0});let Le=v.getChars()||" ";if(Le===" "&&(v.isUnderline()||v.isOverline())&&(Le="\xA0"),xe=g*h-l.get(Le,v.isBold(),v.isItalic()),!p)p=this._document.createElement("span");else if(y&&(M&&ye||!M&&!ye&&v.bg===R)&&(M&&ye&&f.selectionForeground||v.fg===x)&&v.extended.ext===L&&ne===P&&xe===re&&!H&&!C&&!Ee&&S){v.isInvisible()?E+=" ":E+=Le,y++;continue}else y&&(p.textContent=E),p=this._document.createElement("span"),y=0,E="";if(R=v.bg,x=v.fg,L=v.extended.ext,P=ne,re=xe,ye=M,C&&n>=m&&n<=k&&(n=m),!this._coreService.isCursorHidden&&H&&this._coreService.isCursorInitialized){if(F.push("xterm-cursor"),this._coreBrowserService.isFocused)o&&F.push("xterm-cursor-blink"),F.push(s==="bar"?"xterm-cursor-bar":s==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":F.push("xterm-cursor-outline");break;case"block":F.push("xterm-cursor-block");break;case"bar":F.push("xterm-cursor-bar");break;case"underline":F.push("xterm-cursor-underline");break;default:break}}if(v.isBold()&&F.push("xterm-bold"),v.isItalic()&&F.push("xterm-italic"),v.isDim()&&F.push("xterm-dim"),v.isInvisible()?E=" ":E=v.getChars()||" ",v.isUnderline()&&(F.push(`xterm-underline-${v.extended.underlineStyle}`),E===" "&&(E="\xA0"),!v.isUnderlineColorDefault()))if(v.isUnderlineColorRGB())p.style.textDecorationColor=`rgb(${Pt.toColorRGB(v.getUnderlineColor()).join(",")})`;else{let T=v.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&v.isBold()&&T<8&&(T+=8),p.style.textDecorationColor=f.ansi[T].css}v.isOverline()&&(F.push("xterm-overline"),E===" "&&(E="\xA0")),v.isStrikethrough()&&F.push("xterm-strikethrough"),ne&&(p.style.textDecoration="underline");let J=v.getFgColor(),He=v.getFgColorMode(),le=v.getBgColor(),Xe=v.getBgColorMode(),dt=!!v.isInverse();if(dt){let T=J;J=le,le=T;let fi=He;He=Xe,Xe=fi}let We,It,ut=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{T.options.layer!=="top"&&ut||(T.backgroundColorRGB&&(Xe=50331648,le=T.backgroundColorRGB.rgba>>8&16777215,We=T.backgroundColorRGB),T.foregroundColorRGB&&(He=50331648,J=T.foregroundColorRGB.rgba>>8&16777215,It=T.foregroundColorRGB),ut=T.options.layer==="top")}),!ut&&M&&(We=this._coreBrowserService.isFocused?f.selectionBackgroundOpaque:f.selectionInactiveBackgroundOpaque,le=We.rgba>>8&16777215,Xe=50331648,ut=!0,f.selectionForeground&&(He=50331648,J=f.selectionForeground.rgba>>8&16777215,It=f.selectionForeground)),ut&&F.push("xterm-decoration-top");let ze;switch(Xe){case 16777216:case 33554432:ze=f.ansi[le],F.push(`xterm-bg-${le}`);break;case 50331648:ze=V.toColor(le>>16,le>>8&255,le&255),this._addStyle(p,`background-color:#${(le>>>0).toString(16).padStart(6,"0")}`);break;default:dt?(ze=f.foreground,F.push("xterm-bg-257")):ze=f.background}switch(We||v.isDim()&&(We=N.multiplyOpacity(ze,.5)),He){case 16777216:case 33554432:v.isBold()&&J<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(J+=8),this._applyMinimumContrast(p,ze,f.ansi[J],v,We,void 0)||F.push(`xterm-fg-${J}`);break;case 50331648:let T=V.toColor(J>>16&255,J>>8&255,J&255);this._applyMinimumContrast(p,ze,T,v,We,It)||this._addStyle(p,`color:#${J.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(p,ze,f.foreground,v,We,It)||dt&&F.push("xterm-fg-257")}F.length&&(p.className=F.join(" "),F.length=0),!H&&!C&&!Ee&&S?y++:p.textContent=E,xe!==this.defaultSpacing&&(p.style.letterSpacing=`${xe}px`),c.push(p),m=k}return p&&y&&(p.textContent=E),c}_applyMinimumContrast(e,t,i,s,r,n){if(this._optionsService.rawOptions.minimumContrastRatio===1||oh(s.getCode()))return!1;let o=this._getContrastCache(s),h;if(!r&&!n&&(h=o.getColor(t.rgba,i.rgba)),h===void 0){let l=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);h=N.ensureContrastRatio(r??t,n??i,l),o.setColor((r??t).rgba,(n??i).rgba,h??null)}return h?(this._addStyle(e,`color:${h.css}`),!0):!1}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){let i=this._selectionStart,s=this._selectionEnd;return!i||!s?!1:this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0]}};ss=K([w(1,Zr),w(2,he),w(3,Ie),w(4,je),w(5,Tt),w(6,ht)],ss);var hh=class{constructor(e=()=>new lh){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s=0;if(!t&&!i&&e.length===1&&(s=e.charCodeAt(0))<256){if(this._flat[s]!==-9999)return this._flat[s];let o=this._measure(e,0);return o>0&&(this._flat[s]=o),o}let r=e;t&&(r+="B"),i&&(r+="I");let n=this._holey.get(r);if(n===void 0){let o=0;t&&(o|=1),i&&(o|=2),n=this._measure(e,o),n>0&&this._holey.set(r,n)}return n}_measure(e,t){return this._canvasElements[t].measure(e)}},lh=class{constructor(){typeof OffscreenCanvas<"u"?(this._canvas=new OffscreenCanvas(1,1),this._ctx=dr(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=dr(this._canvas.getContext("2d")))}setFont(e,t,i,s){let r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}},ch=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1]){this.clear();return}let r=e.buffers.active.ydisp,n=t[1]-r,o=i[1]-r,h=Math.max(n,0),l=Math.min(o,e.rows-1);if(h>=e.rows||l<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=n,this.viewportEndRow=o,this.viewportCappedStartRow=h,this.viewportCappedEndRow=l,this.startCol=t[0],this.endCol=i[0]}isCellSelected(e,t,i){return this.hasSelection?(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol):!1}};function dh(){return new ch}var Ci="xterm-dom-renderer-owner-",pe="xterm-rows",Ht="xterm-fg-",ur="xterm-bg-",pt="xterm-focus",Wt="xterm-selection",Gt="xterm-cursor-blink-idle",uh=1,rs=class extends I{constructor(e,t,i,s,r,n,o,h,l,a,d,c,u,f){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=n,this._linkifier2=o,this._charSizeService=l,this._optionsService=a,this._bufferService=d,this._coreService=c,this._coreBrowserService=u,this._themeService=f,this._terminalClass=uh++,this._rowElements=[],this._selectionRenderModel=dh(),this.onRequestRedraw=this._register(new b).event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(pe),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add(Wt),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=ah(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(_=>this._injectCss(_))),this._injectCss(this._themeService.colors),this._rowFactory=h.createInstance(ss,document),this._element.classList.add(Ci+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(_=>this._handleLinkHover(_))),this._register(this._linkifier2.onHideLinkUnderline(_=>this._handleLinkLeave(_))),this._cursorBlinkStateManager=new _h(this._rowContainer,this._coreBrowserService),this._register(B(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register(q(()=>this._cursorBlinkStateManager.dispose())),this._register(q(()=>{this._element.classList.remove(Ci+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new hh,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let i of this._rowElements)i.style.width=`${this.dimensions.css.canvas.width}px`,i.style.height=`${this.dimensions.css.cell.height}px`,i.style.lineHeight=`${this.dimensions.css.cell.height}px`,i.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .${pe} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${pe} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${pe} .xterm-dim { color: ${N.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;let i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${pe}.${pt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${pe}.${pt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .${pe}.${pt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${pe}.${Gt} .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${Wt} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Wt} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Wt} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[n,o]of e.ansi.entries())t+=`${this._terminalSelector} .${Ht}${n} { color: ${o.css}; }${this._terminalSelector} .${Ht}${n}.xterm-dim { color: ${N.multiplyOpacity(o,.5).css}; }${this._terminalSelector} .${ur}${n} { background-color: ${o.css}; }`;t+=`${this._terminalSelector} .${Ht}257 { color: ${N.opaque(e.background).css}; }${this._terminalSelector} .${Ht}257.xterm-dim { color: ${N.multiplyOpacity(N.opaque(e.background),.5).css}; }${this._terminalSelector} .${ur}257 { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let i=this._rowElements.length;i<=t;i++){let s=this._document.createElement("div");this._rowContainer.appendChild(s),this._rowElements.push(s)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(pt),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(pt),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,i){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i),this.renderRows(0,this._bufferService.rows-1),!e||!t||(this._selectionRenderModel.update(this._terminal,e,t,i),!this._selectionRenderModel.hasSelection))return;let s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,n=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,h=this._document.createDocumentFragment();if(i){let l=e[0]>t[0];h.appendChild(this._createSelectionElement(n,l?t[0]:e[0],l?e[0]:t[0],o-n+1))}else{let l=s===n?e[0]:0,a=n===r?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(n,l,a));let d=o-n-1;if(h.appendChild(this._createSelectionElement(n+1,0,this._bufferService.cols,d)),n!==o){let c=r===o?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(o,0,c))}}this._selectionContainer.appendChild(h)}_createSelectionElement(e,t,i,s=1){let r=this._document.createElement("div"),n=t*this.dimensions.css.cell.width,o=this.dimensions.css.cell.width*(i-t);return n+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-n),r.style.height=`${s*this.dimensions.css.cell.height}px`,r.style.top=`${e*this.dimensions.css.cell.height}px`,r.style.left=`${n}px`,r.style.width=`${o}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren()}renderRows(e,t){let i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),n=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,h=this._optionsService.rawOptions.cursorInactiveStyle;for(let l=e;l<=t;l++){let a=l+i.ydisp,d=this._rowElements[l],c=i.lines.get(a);if(!d||!c)break;d.replaceChildren(...this._rowFactory.createRow(c,a,a===s,o,h,r,n,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${Ci}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,n){i<0&&(e=0),s<0&&(t=0);let o=this._bufferService.rows-1;i=Math.max(Math.min(i,o),0),s=Math.max(Math.min(s,o),0),r=Math.min(r,this._bufferService.cols);let h=this._bufferService.buffer,l=h.ybase+h.y,a=Math.min(h.x,r-1),d=this._optionsService.rawOptions.cursorBlink,c=this._optionsService.rawOptions.cursorStyle,u=this._optionsService.rawOptions.cursorInactiveStyle;for(let f=i;f<=s;++f){let _=f+h.ydisp,p=this._rowElements[f],y=h.lines.get(_);if(!p||!y)break;p.replaceChildren(...this._rowFactory.createRow(y,_,_===l,c,u,a,d,this.dimensions.css.cell.width,this._widthCache,n?f===i?e:0:-1,n?(f===s?t:r)-1:-1))}}};rs=K([w(7,Ss),w(8,hi),w(9,he),w(10,ce),w(11,je),w(12,Ie),w(13,ht)],rs);var _h=class{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove(Gt),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove(Gt),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add(Gt),this._isIdlePaused=!0,this._idleTimeout=void 0}},ns=class extends I{constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new b),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new ph(this._optionsService))}catch{this._measureStrategy=this._register(new fh(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};ns=K([w(2,he)],ns);var Bn=class extends I{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},fh=class extends Bn{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},ph=class extends Bn{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}},gh=class extends I{constructor(e,t,i){super(),this._textarea=e,this._window=t,this.mainDocument=i,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new vh(this._window)),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new b),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange(s=>this._screenDprMonitor.setWindow(s))),this._register(_e.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(B(this._textarea,"focus",()=>this._isFocused=!0)),this._register(B(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},vh=class extends I{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new ot),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(q(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=B(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}},mh=class extends I{constructor(){super(),this.linkProviders=[],this._register(q(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function Ms(e,t,i){let s=i.getBoundingClientRect(),r=e.getComputedStyle(i),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-s.left-n,t.clientY-s.top-o]}function Sh(e,t,i,s,r,n,o,h,l){if(!n)return;let a=Ms(e,t,i);if(a)return a[0]=Math.ceil((a[0]+(l?o/2:0))/o),a[1]=Math.ceil(a[1]/h),a[0]=Math.min(Math.max(a[0],1),s+(l?1:0)),a[1]=Math.min(Math.max(a[1],1),r),a}var os=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,i,s,r){return Sh(window,e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){let i=Ms(be(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};os=K([w(0,Ne),w(1,hi)],os);var wh=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh())),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,e):e,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,t):t,!this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}},Dn={};eo(Dn,{getSafariVersion:()=>yh,isChromeOS:()=>Pn,isFirefox:()=>Rn,isLegacyEdge:()=>bh,isLinux:()=>Ps,isMac:()=>Dt,isNode:()=>ci,isSafari:()=>Mn,isWindows:()=>Ln});var ci=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),At=ci?"node":navigator.userAgent,Ls=ci?"node":navigator.platform,Rn=At.includes("Firefox"),bh=At.includes("Edge"),Mn=/^((?!chrome|android).)*safari/i.test(At);function yh(){if(!Mn)return 0;let e=At.match(/Version\/(\d+)/);return e===null||e.length<2?0:parseInt(e[1])}var Dt=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(Ls),Ln=["Windows","Win16","Win32","WinCE"].includes(Ls),Ps=Ls.indexOf("Linux")>=0,Pn=/\bCrOS\b/.test(At),Tn=class{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ir){s-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=r}this.clear()}},Ch=class extends Tn{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},kh=class extends Tn{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},oi=!ci&&"requestIdleCallback"in window?kh:Ch,xh=class{constructor(){this._queue=new oi}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}},as=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._rowCount=e,this._optionsService=i,this._charSizeService=s,this._coreService=r,this._coreBrowserService=h,this._renderer=this._register(new ot),this._pausedResizeTask=new xh,this._observerDisposable=this._register(new ot),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new b),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new b),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new wh((a,d)=>this._renderRows(a,d),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new Eh(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(q(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(o.onResize(()=>this._fullRefresh())),this._register(o.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(n.onDecorationRegistered(()=>this._fullRefresh())),this._register(n.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(o.cols,o.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(o.buffer.y,o.buffer.y,void 0,!0))),this._register(l.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(a=>this._registerIntersectionObserver(a,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){let i=new e.IntersectionObserver(s=>this._handleIntersectionChange(s[s.length-1]),{threshold:0});i.observe(t),this._observerDisposable.value=q(()=>i.disconnect())}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(t=>this.refreshRows(t.start,t.end,t.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};as=K([w(2,he),w(3,hi),w(4,je),w(5,Tt),w(6,ce),w(7,Ie),w(8,ht)],as);var Eh=class{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??(this._timeout=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3))}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Bh(e,t,i,s){let r=i.buffer.x,n=i.buffer.y;if(!i.buffer.hasScrollback)return Mh(r,n,e,t,i,s)+di(n,t,i,s)+Lh(r,n,e,t,i,s);let o;if(n===t)return o=r>e?"D":"C",Mt(Math.abs(r-e),Rt(o,s));o=n>t?"D":"C";let h=Math.abs(n-t),l=Rh(n>t?e:r,i)+(h-1)*i.cols+1+Dh(n>t?r:e,i);return Mt(l,Rt(o,s))}function Dh(e,t){return e-1}function Rh(e,t){return t.cols-e}function Mh(e,t,i,s,r,n){return di(t,s,r,n).length===0?"":Mt(On(e,t,e,t-it(t,r),!1,r).length,Rt("D",n))}function di(e,t,i,s){let r=e-it(e,i),n=t-it(t,i),o=Math.abs(r-n)-Ph(e,t,i);return Mt(o,Rt(An(e,t),s))}function Lh(e,t,i,s,r,n){let o;di(t,s,r,n).length>0?o=s-it(s,r):o=t;let h=s,l=Th(e,t,i,s,r,n);return Mt(On(e,o,i,h,l==="C",r).length,Rt(l,n))}function Ph(e,t,i){let s=0,r=e-it(e,i),n=t-it(t,i);for(let o=0;o=0&&e0?o=s-it(s,r):o=t,e=i&&ot?"A":"B"}function On(e,t,i,s,r,n){let o=e,h=t,l="";for(;(o!==i||h!==s)&&h>=0&&hn.cols-1?(l+=n.buffer.translateBufferLineToString(h,!1,e,o),o=0,e=0,h++):!r&&o<0&&(l+=n.buffer.translateBufferLineToString(h,!1,0,e+1),o=n.cols-1,e=o,h--);return l+n.buffer.translateBufferLineToString(h,!1,e,o)}function Rt(e,t){return"\x1B"+(t?"O":"[")+e}function Mt(e,t){e=Math.floor(e);let i="";for(let s=0;sthis._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let e=this.selectionStart,t=this.selectionEnd;return!e||!t?!1:e[1]>t[1]||e[1]===t[1]&&e[0]>t[0]}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function _r(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}var ki=50,Oh=15,Ih=50,Nh=500,Fh="\xA0",Hh=new RegExp(Fh,"g"),hs=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._element=e,this._screenElement=t,this._linkifier=i,this._bufferService=s,this._coreService=r,this._mouseService=n,this._optionsService=o,this._renderService=h,this._coreBrowserService=l,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new ke,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new b),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new b),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=a=>this._handleMouseMove(a),this._mouseUpListener=a=>this._handleMouseUp(a),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(a=>this._handleTrim(a)),this._register(this._bufferService.buffers.onBufferActivate(a=>this._handleBufferActivate(a))),this.enable(),this._model=new Ah(this._bufferService),this._activeSelectionMode=0,this._register(q(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(a=>{a.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";let i=this._bufferService.buffer,s=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return"";let r=e[0]r.replace(Hh," ")).join(Ln?`\r +`).trim(),l=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(h);Vt.register(o,l?.[2]??h,o)}return o}),this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(this._size===1){this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),this._size=0;return}let t=this._listeners,i=t.indexOf(e);if(i===-1)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,t[i]=void 0;let s=this._deliveryQueue.current===this;if(this._size*Uo<=t.length){let r=0;for(let n=0;n0}},Vo=class{constructor(){this.i=-1,this.end=0}enqueue(e,t,i){this.i=0,this.end=i,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},Xi=class{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new se,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new se,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}getZoomLevel(t){return this.mapWindowIdToZoomLevel.get(this.getWindowId(t))??0}setZoomLevel(t,i){if(this.getZoomLevel(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToZoomLevel.set(s,t),this._onDidChangeZoomLevel.fire(s)}getZoomFactor(t){return this.mapWindowIdToZoomFactor.get(this.getWindowId(t))??1}setZoomFactor(t,i){this.mapWindowIdToZoomFactor.set(this.getWindowId(i),t)}setFullscreen(t,i){if(this.isFullscreen(i)===t)return;let s=this.getWindowId(i);this.mapWindowIdToFullScreen.set(s,t),this._onDidChangeFullscreen.fire(s)}isFullscreen(t){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(t))}getWindowId(t){return t.vscodeWindowId}};Xi.INSTANCE=new Xi;var Es=Xi;function Yo(e,t,i){typeof t=="string"&&(t=e.matchMedia(t)),t.addEventListener("change",i)}var Vl=Es.INSTANCE.onDidChangeZoomLevel;function jo(e){return Es.INSTANCE.getZoomFactor(e)}var Yl=Es.INSTANCE.onDidChangeFullscreen,ct=typeof navigator=="object"?navigator.userAgent:"",Gi=ct.indexOf("Firefox")>=0,Yt=ct.indexOf("AppleWebKit")>=0,Bs=ct.indexOf("Chrome")>=0,fn=!Bs&&ct.indexOf("Safari")>=0,jl=ct.indexOf("Electron/")>=0,Xl=ct.indexOf("Android")>=0,jt=!1;if(typeof me.matchMedia=="function"){let e=me.matchMedia("(display-mode: standalone) or (display-mode: window-controls-overlay)"),t=me.matchMedia("(display-mode: fullscreen)");jt=e.matches,Yo(me,e,({matches:i})=>{jt&&t.matches||(jt=i)})}function Xo(){return jt}var ot="en",si=!1,ri=!1,Bt=!1,Go=!1,pn=!1,gn=!1,Jo=!1,Zo=!1,Qo=!1,ea=!1,Ht,Xt=ot,Gs=ot,ta,Ae,Ie=globalThis,ve;typeof Ie.vscode<"u"&&typeof Ie.vscode.process<"u"?ve=Ie.vscode.process:typeof process<"u"&&typeof process?.versions?.node=="string"&&(ve=process);var vn=typeof ve?.versions?.electron=="string",ia=vn&&ve?.type==="renderer";if(typeof ve=="object"){si=ve.platform==="win32",ri=ve.platform==="darwin",Bt=ve.platform==="linux",Go=Bt&&!!ve.env.SNAP&&!!ve.env.SNAP_REVISION,Jo=vn,Qo=!!ve.env.CI||!!ve.env.BUILD_ARTIFACTSTAGINGDIRECTORY,Ht=ot,Xt=ot;let e=ve.env.VSCODE_NLS_CONFIG;if(e)try{let t=JSON.parse(e);Ht=t.userLocale,Gs=t.osLocale,Xt=t.resolvedLanguage||ot,ta=t.languagePack?.translationsConfigFile}catch{}pn=!0}else typeof navigator=="object"&&!ia?(Ae=navigator.userAgent,si=Ae.indexOf("Windows")>=0,ri=Ae.indexOf("Macintosh")>=0,Zo=(Ae.indexOf("Macintosh")>=0||Ae.indexOf("iPad")>=0||Ae.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,Bt=Ae.indexOf("Linux")>=0,ea=Ae?.indexOf("Mobi")>=0,gn=!0,Xt=globalThis._VSCODE_NLS_LANGUAGE||ot,Ht=navigator.language.toLowerCase(),Gs=Ht):console.error("Unable to resolve platform.");var mi=0;ri?mi=1:si?mi=3:Bt&&(mi=2);var mn=si,Re=ri,sa=Bt,Si=pn,ra=gn&&typeof Ie.importScripts=="function",Gl=ra?Ie.origin:void 0,Me=Ae,$e=Xt,na;(e=>{function t(){return $e}e.value=t;function i(){return $e.length===2?$e==="en":$e.length>=3?$e[0]==="e"&&$e[1]==="n"&&$e[2]==="-":!1}e.isDefaultVariant=i;function s(){return $e==="en"}e.isDefault=s})(na||(na={}));var oa=typeof Ie.postMessage=="function"&&!Ie.importScripts,aa=(()=>{if(oa){let e=[];Ie.addEventListener("message",i=>{if(i.data&&i.data.vscodeScheduleAsyncWork)for(let s=0,r=e.length;s{let s=++t;e.push({id:s,callback:i}),Ie.postMessage({vscodeScheduleAsyncWork:s},"*")}}return e=>setTimeout(e)})(),ha=!!(Me&&Me.indexOf("Chrome")>=0),Jl=!!(Me&&Me.indexOf("Firefox")>=0),Zl=!!(!ha&&Me&&Me.indexOf("Safari")>=0),Ql=!!(Me&&Me.indexOf("Edg/")>=0),ec=!!(Me&&Me.indexOf("Android")>=0),Ue=typeof navigator=="object"?navigator:{},tc={clipboard:{writeText:Si||document.queryCommandSupported&&document.queryCommandSupported("copy")||!!(Ue&&Ue.clipboard&&Ue.clipboard.writeText),readText:Si||!!(Ue&&Ue.clipboard&&Ue.clipboard.readText)},keyboard:Si||Xo()?0:Ue.keyboard||fn?1:2,touch:"ontouchstart"in me||Ue.maxTouchPoints>0,pointerEvents:me.PointerEvent&&("ontouchstart"in me||navigator.maxTouchPoints>0)},Ds=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||0}},wi=new Ds,Js=new Ds,Zs=new Ds,la=new Array(230),Sn;(e=>{function t(h){return wi.keyCodeToStr(h)}e.toString=t;function i(h){return wi.strToKeyCode(h)}e.fromString=i;function s(h){return Js.keyCodeToStr(h)}e.toUserSettingsUS=s;function r(h){return Zs.keyCodeToStr(h)}e.toUserSettingsGeneral=r;function n(h){return Js.strToKeyCode(h)||Zs.strToKeyCode(h)}e.fromUserSettings=n;function o(h){if(h>=98&&h<=113)return null;switch(h){case 16:return"Up";case 18:return"Down";case 15:return"Left";case 17:return"Right"}return wi.keyCodeToStr(h)}e.toElectronAccelerator=o})(Sn||(Sn={}));var ca=class wn{constructor(t,i,s,r,n){this.ctrlKey=t,this.shiftKey=i,this.altKey=s,this.metaKey=r,this.keyCode=n}equals(t){return t instanceof wn&&this.ctrlKey===t.ctrlKey&&this.shiftKey===t.shiftKey&&this.altKey===t.altKey&&this.metaKey===t.metaKey&&this.keyCode===t.keyCode}getHashCode(){let t=this.ctrlKey?"1":"0",i=this.shiftKey?"1":"0",s=this.altKey?"1":"0",r=this.metaKey?"1":"0";return`K${t}${i}${s}${r}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new da([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}},da=class{constructor(e){if(e.length===0)throw yo("chords");this.chords=e}getHashCode(){let e="";for(let t=0,i=this.chords.length;t{function t(i){return i===e.None||i===e.Cancelled||i instanceof wa?!0:!i||typeof i!="object"?!1:typeof i.isCancellationRequested=="boolean"&&typeof i.onCancellationRequested=="function"}e.isCancellationToken=t,e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:di.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:bn})})(Sa||(Sa={}));var wa=class{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?bn:(this._emitter||(this._emitter=new se),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}};var Rs=class{constructor(e,t){this._isDisposed=!1,this._token=-1,typeof e=="function"&&typeof t=="number"&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new zi("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new zi("Calling 'setIfNotSet' on a disposed TimeoutTimer");this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},ba=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this.isDisposed)throw new zi("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();let s=i.setInterval(()=>{e()},t);this.disposable=je(()=>{i.clearInterval(s),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}},ya,bi;(function(){typeof globalThis.requestIdleCallback!="function"||typeof globalThis.cancelIdleCallback!="function"?bi=(e,t)=>{aa(()=>{if(i)return;let s=Date.now()+15;t(Object.freeze({didTimeout:!0,timeRemaining(){return Math.max(0,s-Date.now())}}))});let i=!1;return{dispose(){i||(i=!0)}}}:bi=(e,t,i)=>{let s=e.requestIdleCallback(t,typeof i=="number"?{timeout:i}:void 0),r=!1;return{dispose(){r||(r=!0,e.cancelIdleCallback(s))}}},ya=e=>bi(globalThis,e)})();var Ca;(e=>{async function t(s){let r,n=await Promise.all(s.map(o=>o.then(h=>h,h=>{r||(r=h)})));if(typeof r<"u")throw r;return n}e.settled=t;function i(s){return new Promise(async(r,n)=>{try{await s(r,n)}catch(o){n(o)}})}e.withAsyncBody=i})(Ca||(Ca={}));var tr=class we{static fromArray(t){return new we(i=>{i.emitMany(t)})}static fromPromise(t){return new we(async i=>{i.emitMany(await t)})}static fromPromises(t){return new we(async i=>{await Promise.all(t.map(async s=>i.emitOne(await s)))})}static merge(t){return new we(async i=>{await Promise.all(t.map(async s=>{for await(let r of s)i.emitOne(r)}))})}constructor(t,i){this._state=0,this._results=[],this._error=null,this._onReturn=i,this._onStateChanged=new se,queueMicrotask(async()=>{let s={emitOne:r=>this.emitOne(r),emitMany:r=>this.emitMany(r),reject:r=>this.reject(r)};try{await Promise.resolve(t(s)),this.resolve()}catch(r){this.reject(r)}finally{s.emitOne=void 0,s.emitMany=void 0,s.reject=void 0}})}[Symbol.asyncIterator](){let t=0;return{next:async()=>{do{if(this._state===2)throw this._error;if(t(this._onReturn?.(),{done:!0,value:void 0})}}static map(t,i){return new we(async s=>{for await(let r of t)s.emitOne(i(r))})}map(t){return we.map(this,t)}static filter(t,i){return new we(async s=>{for await(let r of t)i(r)&&s.emitOne(r)})}filter(t){return we.filter(this,t)}static coalesce(t){return we.filter(t,i=>!!i)}coalesce(){return we.coalesce(this)}static async toPromise(t){let i=[];for await(let s of t)i.push(s);return i}toPromise(){return we.toPromise(this)}emitOne(t){this._state===0&&(this._results.push(t),this._onStateChanged.fire())}emitMany(t){this._state===0&&(this._results=this._results.concat(t),this._onStateChanged.fire())}resolve(){this._state===0&&(this._state=1,this._onStateChanged.fire())}reject(t){this._state===0&&(this._state=2,this._error=t,this._onStateChanged.fire())}};tr.EMPTY=tr.fromArray([]);function ka(e){return 55296<=e&&e<=56319}function ir(e){return 56320<=e&&e<=57343}function xa(e,t){return(e-55296<<10)+(t-56320)+65536}function Ea(e){return Ms(e,0)}function Ms(e,t){switch(typeof e){case"object":return e===null?Oe(349,t):Array.isArray(e)?Da(e,t):Ra(e,t);case"string":return yn(e,t);case"boolean":return Ba(e,t);case"number":return Oe(e,t);case"undefined":return Oe(937,t);default:return Oe(617,t)}}function Oe(e,t){return(t<<5)-t+e|0}function Ba(e,t){return Oe(e?433:863,t)}function yn(e,t){t=Oe(149417,t);for(let i=0,s=e.length;iMs(s,i),t)}function Ra(e,t){return t=Oe(181387,t),Object.keys(e).sort().reduce((i,s)=>(i=yn(s,i),Ms(e[s],i)),t)}function yi(e,t,i=32){let s=i-t,r=~((1<>>s)>>>0}function sr(e,t=0,i=e.byteLength,s=0){for(let r=0;ri.toString(16).padStart(2,"0")).join(""):Ma((e>>>0).toString(16),t/4)}var La=class Cn{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(67),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(t){let i=t.length;if(i===0)return;let s=this._buff,r=this._buffLen,n=this._leftoverHighSurrogate,o,h;for(n!==0?(o=n,h=-1,n=0):(o=t.charCodeAt(0),h=0);;){let l=o;if(ka(o))if(h+1>>6,t[i++]=128|(s&63)>>>0):s<65536?(t[i++]=224|(s&61440)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0):(t[i++]=240|(s&1835008)>>>18,t[i++]=128|(s&258048)>>>12,t[i++]=128|(s&4032)>>>6,t[i++]=128|(s&63)>>>0),i>=64&&(this._step(),i-=64,this._totalLen+=64,t[0]=t[64],t[1]=t[65],t[2]=t[66]),i}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),ft(this._h0)+ft(this._h1)+ft(this._h2)+ft(this._h3)+ft(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,sr(this._buff,this._buffLen),this._buffLen>56&&(this._step(),sr(this._buff));let t=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(t/4294967296),!1),this._buffDV.setUint32(60,t%4294967296,!1),this._step()}_step(){let t=Cn._bigBlock32,i=this._buffDV;for(let c=0;c<64;c+=4)t.setUint32(c,i.getUint32(c,!1),!1);for(let c=64;c<320;c+=4)t.setUint32(c,yi(t.getUint32(c-12,!1)^t.getUint32(c-32,!1)^t.getUint32(c-56,!1)^t.getUint32(c-64,!1),1),!1);let s=this._h0,r=this._h1,n=this._h2,o=this._h3,h=this._h4,l,a,d;for(let c=0;c<80;c++)c<20?(l=r&n|~r&o,a=1518500249):c<40?(l=r^n^o,a=1859775393):c<60?(l=r&n|r&o|n&o,a=2400959708):(l=r^n^o,a=3395469782),d=yi(s,5)+l+h+a+t.getUint32(c*4,!1)&4294967295,h=o,o=n,n=yi(r,30),r=s,s=d;this._h0=this._h0+s&4294967295,this._h1=this._h1+r&4294967295,this._h2=this._h2+n&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+h&4294967295}};La._bigBlock32=new DataView(new ArrayBuffer(320));var{registerWindow:ic,getWindow:be,getDocument:sc,getWindows:rc,getWindowsCount:nc,getWindowId:rr,getWindowById:oc,hasWindow:ac,onDidRegisterWindow:Pa,onWillUnregisterWindow:hc,onDidUnregisterWindow:lc}=(function(){let e=new Map,t={window:me,disposables:new tt};e.set(me.vscodeWindowId,t);let i=new se,s=new se,r=new se;function n(o,h){return(typeof o=="number"?e.get(o):void 0)??(h?t:void 0)}return{onDidRegisterWindow:i.event,onWillUnregisterWindow:r.event,onDidUnregisterWindow:s.event,registerWindow(o){if(e.has(o.vscodeWindowId))return Se.None;let h=new tt,l={window:o,disposables:h.add(new tt)};return e.set(o.vscodeWindowId,l),h.add(je(()=>{e.delete(o.vscodeWindowId),s.fire(o)})),h.add(B(o,Z.BEFORE_UNLOAD,()=>{r.fire(o)})),i.fire(l),h},getWindows(){return e.values()},getWindowsCount(){return e.size},getWindowId(o){return o.vscodeWindowId},hasWindow(o){return e.has(o)},getWindowById:n,getWindow(o){let h=o;if(h?.ownerDocument?.defaultView)return h.ownerDocument.defaultView.window;let l=o;return l?.view?l.view.window:me},getDocument(o){return be(o).document}}})(),Ta=class{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}};function B(e,t,i,s){return new Ta(e,t,i,s)}function Aa(e,t){return function(i){return t(new bt(e,i))}}function Oa(e){return function(t){return e(new Ji(t))}}var nr=function(e,t,i,s){let r=i;return t==="click"||t==="mousedown"||t==="contextmenu"?r=Aa(be(e),i):(t==="keydown"||t==="keypress"||t==="keyup")&&(r=Oa(i)),B(e,t,r,s)},Ia,ni,Na=class extends ba{constructor(e){super(),this.defaultTarget=e&&be(e)}cancelAndSet(e,t,i){return super.cancelAndSet(e,t,i??this.defaultTarget)}},Ci=class{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){Ut(e)}}static sort(e,t){return t.priority-e.priority}};(function(){let e=new Map,t=new Map,i=new Map,s=new Map,r=n=>{i.set(n,!1);let o=e.get(n)??[];for(t.set(n,o),e.set(n,[]),s.set(n,!0);o.length>0;)o.sort(Ci.sort),o.shift().execute();s.set(n,!1)};ni=(n,o,h=0)=>{let l=rr(n),a=new Ci(o,h),d=e.get(l);return d||(d=[],e.set(l,d)),d.push(a),i.get(l)||(i.set(l,!0),n.requestAnimationFrame(()=>r(l))),a},Ia=(n,o,h)=>{let l=rr(n);if(s.get(l)){let a=new Ci(o,h),d=t.get(l);return d||(d=[],t.set(l,d)),d.push(a),a}else return ni(n,o,h)}})();var or=class Gt{constructor(t,i){this.width=t,this.height=i}with(t=this.width,i=this.height){return t!==this.width||i!==this.height?new Gt(t,i):this}static is(t){return typeof t=="object"&&typeof t.height=="number"&&typeof t.width=="number"}static lift(t){return t instanceof Gt?t:new Gt(t.width,t.height)}static equals(t,i){return t===i?!0:!t||!i?!1:t.width===i.width&&t.height===i.height}};or.None=new or(0,0);function Fa(e){let t=e.getBoundingClientRect(),i=be(e);return{left:t.left+i.scrollX,top:t.top+i.scrollY,width:t.width,height:t.height}}var cc=new class{constructor(){this.mutationObservers=new Map}observe(e,t,i){let s=this.mutationObservers.get(e);s||(s=new Map,this.mutationObservers.set(e,s));let r=Ea(i),n=s.get(r);if(n)n.users+=1;else{let o=new se,h=new MutationObserver(a=>o.fire(a));h.observe(e,i);let l=n={users:1,observer:h,onDidMutate:o.event};t.add(je(()=>{l.users-=1,l.users===0&&(o.dispose(),h.disconnect(),s?.delete(r),s?.size===0&&this.mutationObservers.delete(e))})),s.set(r,n)}return n.onDidMutate}},Z={CLICK:"click",AUXCLICK:"auxclick",DBLCLICK:"dblclick",MOUSE_UP:"mouseup",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_MOVE:"mousemove",MOUSE_OUT:"mouseout",MOUSE_ENTER:"mouseenter",MOUSE_LEAVE:"mouseleave",MOUSE_WHEEL:"wheel",POINTER_UP:"pointerup",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_LEAVE:"pointerleave",CONTEXT_MENU:"contextmenu",WHEEL:"wheel",KEY_DOWN:"keydown",KEY_PRESS:"keypress",KEY_UP:"keyup",LOAD:"load",BEFORE_UNLOAD:"beforeunload",UNLOAD:"unload",PAGE_SHOW:"pageshow",PAGE_HIDE:"pagehide",PASTE:"paste",ABORT:"abort",ERROR:"error",RESIZE:"resize",SCROLL:"scroll",FULLSCREEN_CHANGE:"fullscreenchange",WK_FULLSCREEN_CHANGE:"webkitfullscreenchange",SELECT:"select",CHANGE:"change",SUBMIT:"submit",RESET:"reset",FOCUS:"focus",FOCUS_IN:"focusin",FOCUS_OUT:"focusout",BLUR:"blur",INPUT:"input",STORAGE:"storage",DRAG_START:"dragstart",DRAG:"drag",DRAG_ENTER:"dragenter",DRAG_LEAVE:"dragleave",DRAG_OVER:"dragover",DROP:"drop",DRAG_END:"dragend",ANIMATION_START:Yt?"webkitAnimationStart":"animationstart",ANIMATION_END:Yt?"webkitAnimationEnd":"animationend",ANIMATION_ITERATION:Yt?"webkitAnimationIteration":"animationiteration"},Ha=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function kn(e,t,i,...s){let r=Ha.exec(t);if(!r)throw new Error("Bad use of emmet");let n=r[1]||"div",o;return e!=="http://www.w3.org/1999/xhtml"?o=document.createElementNS(e,n):o=document.createElement(n),r[3]&&(o.id=r[3]),r[4]&&(o.className=r[4].replace(/\./g," ").trim()),i&&Object.entries(i).forEach(([h,l])=>{typeof l>"u"||(/^on\w+$/.test(h)?o[h]=l:h==="selected"?l&&o.setAttribute(h,"true"):o.setAttribute(h,l))}),o.append(...s),o}function Wa(e,t,...i){return kn("http://www.w3.org/1999/xhtml",e,t,...i)}Wa.SVG=function(e,t,...i){return kn("http://www.w3.org/2000/svg",e,t,...i)};var za=class{constructor(e){this.domNode=e,this._maxWidth="",this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._paddingTop="",this._paddingLeft="",this._paddingBottom="",this._paddingRight="",this._fontFamily="",this._fontWeight="",this._fontSize="",this._fontStyle="",this._fontFeatureSettings="",this._fontVariationSettings="",this._textDecoration="",this._lineHeight="",this._letterSpacing="",this._className="",this._display="",this._position="",this._visibility="",this._color="",this._backgroundColor="",this._layerHint=!1,this._contain="none",this._boxShadow=""}setMaxWidth(e){let t=de(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){let t=de(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){let t=de(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){let t=de(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){let t=de(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){let t=de(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){let t=de(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){let t=de(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){let t=de(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){let t=de(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){let t=de(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){let t=de(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){let t=de(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){let t=de(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?"translate3d(0px, 0px, 0px)":"")}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}};function de(e){return typeof e=="number"?`${e}px`:e}function Dt(e){return new za(e)}var xn=class{constructor(){this._hooks=new tt,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let i=this._onStopCallback;this._onStopCallback=null,e&&i&&i(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,i,s,r){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=s,this._onStopCallback=r;let n=e;try{e.setPointerCapture(t),this._hooks.add(je(()=>{try{e.releasePointerCapture(t)}catch{}}))}catch{n=be(e)}this._hooks.add(B(n,Z.POINTER_MOVE,o=>{if(o.buttons!==i){this.stopMonitoring(!0);return}o.preventDefault(),this._pointerMoveCallback(o)})),this._hooks.add(B(n,Z.POINTER_UP,o=>this.stopMonitoring(!0)))}};function Ka(e,t,i){let s=null,r=null;if(typeof i.value=="function"?(s="value",r=i.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof i.get=="function"&&(s="get",r=i.get),!r)throw new Error("not supported");let n=`$memoize$${t}`;i[s]=function(...o){return this.hasOwnProperty(n)||Object.defineProperty(this,n,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,o)}),this[n]}}var Ce;(e=>(e.Tap="-xterm-gesturetap",e.Change="-xterm-gesturechange",e.Start="-xterm-gesturestart",e.End="-xterm-gesturesend",e.Contextmenu="-xterm-gesturecontextmenu"))(Ce||(Ce={}));var yt=class ae extends Se{constructor(){super(),this.dispatched=!1,this.targets=new Ys,this.ignoreTargets=new Ys,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(di.runAndSubscribe(Pa,({window:t,disposables:i})=>{i.add(B(t.document,"touchstart",s=>this.onTouchStart(s),{passive:!1})),i.add(B(t.document,"touchend",s=>this.onTouchEnd(t,s))),i.add(B(t.document,"touchmove",s=>this.onTouchMove(s),{passive:!1}))},{window:me,disposables:this._store}))}static addTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Vs(new ae));let i=ae.INSTANCE.targets.push(t);return je(i)}static ignoreTarget(t){if(!ae.isTouchDevice())return Se.None;ae.INSTANCE||(ae.INSTANCE=Vs(new ae));let i=ae.INSTANCE.ignoreTargets.push(t);return je(i)}static isTouchDevice(){return"ontouchstart"in me||navigator.maxTouchPoints>0}dispose(){this.handle&&(this.handle.dispose(),this.handle=null),super.dispose()}onTouchStart(t){let i=Date.now();this.handle&&(this.handle.dispose(),this.handle=null);for(let s=0,r=t.targetTouches.length;s=ae.HOLD_DELAY&&Math.abs(l.initialPageX-fe(l.rollingPageX))<30&&Math.abs(l.initialPageY-fe(l.rollingPageY))<30){let d=this.newGestureEvent(Ce.Contextmenu,l.initialTarget);d.pageX=fe(l.rollingPageX),d.pageY=fe(l.rollingPageY),this.dispatchEvent(d)}else if(r===1){let d=fe(l.rollingPageX),c=fe(l.rollingPageY),u=fe(l.rollingTimestamps)-l.rollingTimestamps[0],f=d-l.rollingPageX[0],_=c-l.rollingPageY[0],p=[...this.targets].filter(y=>l.initialTarget instanceof Node&&y.contains(l.initialTarget));this.inertia(t,p,s,Math.abs(f)/u,f>0?1:-1,d,Math.abs(_)/u,_>0?1:-1,c)}this.dispatchEvent(this.newGestureEvent(Ce.End,l.initialTarget)),delete this.activeTouches[h.identifier]}this.dispatched&&(i.preventDefault(),i.stopPropagation(),this.dispatched=!1)}newGestureEvent(t,i){let s=document.createEvent("CustomEvent");return s.initEvent(t,!1,!0),s.initialTarget=i,s.tapCount=0,s}dispatchEvent(t){if(t.type===Ce.Tap){let i=new Date().getTime(),s=0;i-this._lastSetTapCountTime>ae.CLEAR_TAP_COUNT_TIME?s=1:s=2,this._lastSetTapCountTime=i,t.tapCount=s}else(t.type===Ce.Change||t.type===Ce.Contextmenu)&&(this._lastSetTapCountTime=0);if(t.initialTarget instanceof Node){for(let s of this.ignoreTargets)if(s.contains(t.initialTarget))return;let i=[];for(let s of this.targets)if(s.contains(t.initialTarget)){let r=0,n=t.initialTarget;for(;n&&n!==s;)r++,n=n.parentElement;i.push([r,s])}i.sort((s,r)=>s[0]-r[0]);for(let[s,r]of i)r.dispatchEvent(t),this.dispatched=!0}}inertia(t,i,s,r,n,o,h,l,a){this.handle=ni(t,()=>{let d=Date.now(),c=d-s,u=0,f=0,_=!0;r+=ae.SCROLL_FRICTION*c,h+=ae.SCROLL_FRICTION*c,r>0&&(_=!1,u=n*r*c),h>0&&(_=!1,f=l*h*c);let p=this.newGestureEvent(Ce.Change);p.translationX=u,p.translationY=f,i.forEach(y=>y.dispatchEvent(p)),_||this.inertia(t,i,d,r,n,o+u,h,l,a+f)})}onTouchMove(t){let i=Date.now();for(let s=0,r=t.changedTouches.length;s3&&(o.rollingPageX.shift(),o.rollingPageY.shift(),o.rollingTimestamps.shift()),o.rollingPageX.push(n.pageX),o.rollingPageY.push(n.pageY),o.rollingTimestamps.push(i)}this.dispatched&&(t.preventDefault(),t.stopPropagation(),this.dispatched=!1)}};yt.SCROLL_FRICTION=-.005,yt.HOLD_DELAY=700,yt.CLEAR_TAP_COUNT_TIME=400,K([Ka],yt,"isTouchDevice",1);var En=yt,Ls=class extends Se{onclick(e,t){this._register(B(e,Z.CLICK,i=>t(new bt(be(e),i))))}onmousedown(e,t){this._register(B(e,Z.MOUSE_DOWN,i=>t(new bt(be(e),i))))}onmouseover(e,t){this._register(B(e,Z.MOUSE_OVER,i=>t(new bt(be(e),i))))}onmouseleave(e,t){this._register(B(e,Z.MOUSE_LEAVE,i=>t(new bt(be(e),i))))}onkeydown(e,t){this._register(B(e,Z.KEY_DOWN,i=>t(new Ji(i))))}onkeyup(e,t){this._register(B(e,Z.KEY_UP,i=>t(new Ji(i))))}oninput(e,t){this._register(B(e,Z.INPUT,t))}onblur(e,t){this._register(B(e,Z.BLUR,t))}onfocus(e,t){this._register(B(e,Z.FOCUS,t))}onchange(e,t){this._register(B(e,Z.CHANGE,t))}ignoreGesture(e){return En.ignoreTarget(e)}},ar=11,$a=class extends Ls{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=e.bgWidth+"px",this.bgDomNode.style.height=e.bgHeight+"px",typeof e.top<"u"&&(this.bgDomNode.style.top="0px"),typeof e.left<"u"&&(this.bgDomNode.style.left="0px"),typeof e.bottom<"u"&&(this.bgDomNode.style.bottom="0px"),typeof e.right<"u"&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=e.className,this.domNode.style.position="absolute",this.domNode.style.width=ar+"px",this.domNode.style.height=ar+"px",typeof e.top<"u"&&(this.domNode.style.top=e.top+"px"),typeof e.left<"u"&&(this.domNode.style.left=e.left+"px"),typeof e.bottom<"u"&&(this.domNode.style.bottom=e.bottom+"px"),typeof e.right<"u"&&(this.domNode.style.right=e.right+"px"),this._pointerMoveMonitor=this._register(new xn),this._register(nr(this.bgDomNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._register(nr(this.domNode,Z.POINTER_DOWN,t=>this._arrowPointerDown(t))),this._pointerdownRepeatTimer=this._register(new Na),this._pointerdownScheduleRepeatTimer=this._register(new Rs)}_arrowPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,be(e))};this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(t,200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,i=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault()}},Ua=class Zi{constructor(t,i,s,r,n,o,h){this._forceIntegerValues=t,this._scrollStateBrand=void 0,this._forceIntegerValues&&(i=i|0,s=s|0,r=r|0,n=n|0,o=o|0,h=h|0),this.rawScrollLeft=r,this.rawScrollTop=h,i<0&&(i=0),r+i>s&&(r=s-i),r<0&&(r=0),n<0&&(n=0),h+n>o&&(h=o-n),h<0&&(h=0),this.width=i,this.scrollWidth=s,this.scrollLeft=r,this.height=n,this.scrollHeight=o,this.scrollTop=h}equals(t){return this.rawScrollLeft===t.rawScrollLeft&&this.rawScrollTop===t.rawScrollTop&&this.width===t.width&&this.scrollWidth===t.scrollWidth&&this.scrollLeft===t.scrollLeft&&this.height===t.height&&this.scrollHeight===t.scrollHeight&&this.scrollTop===t.scrollTop}withScrollDimensions(t,i){return new Zi(this._forceIntegerValues,typeof t.width<"u"?t.width:this.width,typeof t.scrollWidth<"u"?t.scrollWidth:this.scrollWidth,i?this.rawScrollLeft:this.scrollLeft,typeof t.height<"u"?t.height:this.height,typeof t.scrollHeight<"u"?t.scrollHeight:this.scrollHeight,i?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new Zi(this._forceIntegerValues,this.width,this.scrollWidth,typeof t.scrollLeft<"u"?t.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof t.scrollTop<"u"?t.scrollTop:this.rawScrollTop)}createScrollEvent(t,i){let s=this.width!==t.width,r=this.scrollWidth!==t.scrollWidth,n=this.scrollLeft!==t.scrollLeft,o=this.height!==t.height,h=this.scrollHeight!==t.scrollHeight,l=this.scrollTop!==t.scrollTop;return{inSmoothScrolling:i,oldWidth:t.width,oldScrollWidth:t.scrollWidth,oldScrollLeft:t.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:t.height,oldScrollHeight:t.scrollHeight,oldScrollTop:t.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:s,scrollWidthChanged:r,scrollLeftChanged:n,heightChanged:o,scrollHeightChanged:h,scrollTopChanged:l}}},qa=class extends Se{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new Ua(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let i=this._state.withScrollDimensions(e,t);this._setState(i,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>"u"?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>"u"?this._smoothScrolling.to.scrollTop:e.scrollTop};let i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;t?s=new lr(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):s=this._smoothScrolling.combine(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{let i=this._state.withScrollPosition(e);this._smoothScrolling=lr.start(this._state,i,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),!!this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}},hr=class{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}};function ki(e,t){let i=t-e;return function(s){return e+i*ja(s)}}function Va(e,t,i){return function(s){return s2.5*s){let r,n;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" fade":"")))}},Ga=140,Bn=class extends Ls{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new Xa(e.visibility,"visible scrollbar "+e.extraScrollbarClassName,"invisible scrollbar "+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new xn),this._shouldRender=!0,this.domNode=Dt(document.createElement("div")),this.domNode.setAttribute("role","presentation"),this.domNode.setAttribute("aria-hidden","true"),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition("absolute"),this._register(B(this.domNode.domNode,Z.POINTER_DOWN,t=>this._domNodePointerDown(t)))}_createArrow(e){let t=this._register(new $a(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,i,s){this.slider=Dt(document.createElement("div")),this.slider.setClassName("slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),typeof i=="number"&&this.slider.setWidth(i),typeof s=="number"&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(B(this.slider.domNode,Z.POINTER_DOWN,r=>{r.button===0&&(r.preventDefault(),this._sliderPointerDown(r))})),this.onclick(this.slider.domNode,r=>{r.leftButton&&r.stopPropagation()})}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){let t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?e.button===0&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,i;if(e.target===this.domNode.domNode&&typeof e.offsetX=="number"&&typeof e.offsetY=="number")t=e.offsetX,i=e.offsetY;else{let r=Fa(this.domNode.domNode);t=e.pageX-r.left,i=e.pageY-r.top}let s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,r=>{let n=this._sliderOrthogonalPointerPosition(r),o=Math.abs(n-i);if(mn&&o>Ga){this._setDesiredScrollPositionNow(s.getScrollPosition());return}let h=this._sliderPointerPosition(r)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(h))},()=>{this.slider.toggleClassName("active",!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(e){let t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}},Dn=class es{constructor(t,i,s,r,n,o){this._scrollbarSize=Math.round(i),this._oppositeScrollbarSize=Math.round(s),this._arrowSize=Math.round(t),this._visibleSize=r,this._scrollSize=n,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new es(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(t){let i=Math.round(t);return this._visibleSize!==i?(this._visibleSize=i,this._refreshComputedValues(),!0):!1}setScrollSize(t){let i=Math.round(t);return this._scrollSize!==i?(this._scrollSize=i,this._refreshComputedValues(),!0):!1}setScrollPosition(t){let i=Math.round(t);return this._scrollPosition!==i?(this._scrollPosition=i,this._refreshComputedValues(),!0):!1}setScrollbarSize(t){this._scrollbarSize=Math.round(t)}setOppositeScrollbarSize(t){this._oppositeScrollbarSize=Math.round(t)}static _computeValues(t,i,s,r,n){let o=Math.max(0,s-t),h=Math.max(0,o-2*i),l=r>0&&r>s;if(!l)return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(h),computedSliderRatio:0,computedSliderPosition:0};let a=Math.round(Math.max(20,Math.floor(s*h/r))),d=(h-a)/(r-s),c=n*d;return{computedAvailableSize:Math.round(o),computedIsNeeded:l,computedSliderSize:Math.round(a),computedSliderRatio:d,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){let t=es._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=t.computedAvailableSize,this._computedIsNeeded=t.computedIsNeeded,this._computedSliderSize=t.computedSliderSize,this._computedSliderRatio=t.computedSliderRatio,this._computedSliderPosition=t.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize-this._computedSliderSize/2;return Math.round(i/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(t){if(!this._computedIsNeeded)return 0;let i=t-this._arrowSize,s=this._scrollPosition;return i0&&Math.abs(t.deltaY)>0)return 1;let s=.5;if((!this._isAlmostInt(t.deltaX)||!this._isAlmostInt(t.deltaY))&&(s+=.25),i){let r=Math.abs(t.deltaX),n=Math.abs(t.deltaY),o=Math.abs(i.deltaX),h=Math.abs(i.deltaY),l=Math.max(Math.min(r,o),1),a=Math.max(Math.min(n,h),1),d=Math.max(r,o),c=Math.max(n,h);d%l===0&&c%a===0&&(s-=.5)}return Math.min(Math.max(s,0),1)}_isAlmostInt(t){return Math.abs(Math.round(t)-t)<.01}};ts.INSTANCE=new ts;var th=ts,ih=class extends Ls{constructor(e,t,i){super(),this._onScroll=this._register(new se),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new se),this.onWillScroll=this._onWillScroll.event,this._options=rh(t),this._scrollable=i,this._register(this._scrollable.onScroll(r=>{this._onWillScroll.fire(r),this._onDidScroll(r),this._onScroll.fire(r)}));let s={onMouseWheel:r=>this._onMouseWheel(r),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new Za(this._scrollable,this._options,s)),this._horizontalScrollbar=this._register(new Ja(this._scrollable,this._options,s)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=Dt(document.createElement("div")),this._leftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=Dt(document.createElement("div")),this._topShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=Dt(document.createElement("div")),this._topLeftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,r=>this._onMouseOver(r)),this.onmouseleave(this._listenOnDomNode,r=>this._onMouseLeave(r)),this._hideTimeout=this._register(new Rs),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=ii(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,Re&&(this._options.className+=" mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){typeof e.handleMouseWheel<"u"&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<"u"&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<"u"&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<"u"&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<"u"&&(this._options.horizontal=e.horizontal),typeof e.vertical<"u"&&(this._options.vertical=e.vertical),typeof e.horizontalScrollbarSize<"u"&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<"u"&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<"u"&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new er(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=ii(this._mouseWheelToDispose),e)){let t=i=>{this._onMouseWheel(new er(i))};this._mouseWheelToDispose.push(B(this._listenOnDomNode,Z.MOUSE_WHEEL,t,{passive:!1}))}}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=th.INSTANCE;dr&&t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let r=e.deltaY*this._options.mouseWheelScrollSensitivity,n=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&n+r===0?n=r=0:Math.abs(r)>=Math.abs(n)?n=0:r=0),this._options.flipAxes&&([r,n]=[n,r]);let o=!Re&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||o)&&!n&&(n=r,r=0),e.browserEvent&&e.browserEvent.altKey&&(n=n*this._options.fastScrollSensitivity,r=r*this._options.fastScrollSensitivity);let h=this._scrollable.getFutureScrollPosition(),l={};if(r){let a=cr*r,d=h.scrollTop-(a<0?Math.floor(a):Math.ceil(a));this._verticalScrollbar.writeScrollPosition(l,d)}if(n){let a=cr*n,d=h.scrollLeft-(a<0?Math.floor(a):Math.ceil(a));this._horizontalScrollbar.writeScrollPosition(l,d)}l=this._scrollable.validateScrollPosition(l),(h.scrollLeft!==l.scrollLeft||h.scrollTop!==l.scrollTop)&&(dr&&this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(l):this._scrollable.setScrollPositionNow(l),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" left":"",r=t?" top":"",n=i||t?" top-left-corner":"";this._leftShadowDomNode.setClassName(`shadow${s}`),this._topShadowDomNode.setClassName(`shadow${r}`),this._topLeftShadowDomNode.setClassName(`shadow${n}${r}${s}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),Qa)}},sh=class extends ih{constructor(e,t,i){super(e,t,i)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function rh(e){let t={lazyRender:typeof e.lazyRender<"u"?e.lazyRender:!1,className:typeof e.className<"u"?e.className:"",useShadows:typeof e.useShadows<"u"?e.useShadows:!0,handleMouseWheel:typeof e.handleMouseWheel<"u"?e.handleMouseWheel:!0,flipAxes:typeof e.flipAxes<"u"?e.flipAxes:!1,consumeMouseWheelIfScrollbarIsNeeded:typeof e.consumeMouseWheelIfScrollbarIsNeeded<"u"?e.consumeMouseWheelIfScrollbarIsNeeded:!1,alwaysConsumeMouseWheel:typeof e.alwaysConsumeMouseWheel<"u"?e.alwaysConsumeMouseWheel:!1,scrollYToX:typeof e.scrollYToX<"u"?e.scrollYToX:!1,mouseWheelScrollSensitivity:typeof e.mouseWheelScrollSensitivity<"u"?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof e.fastScrollSensitivity<"u"?e.fastScrollSensitivity:5,scrollPredominantAxis:typeof e.scrollPredominantAxis<"u"?e.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof e.mouseWheelSmoothScroll<"u"?e.mouseWheelSmoothScroll:!0,arrowSize:typeof e.arrowSize<"u"?e.arrowSize:11,listenOnDomNode:typeof e.listenOnDomNode<"u"?e.listenOnDomNode:null,horizontal:typeof e.horizontal<"u"?e.horizontal:1,horizontalScrollbarSize:typeof e.horizontalScrollbarSize<"u"?e.horizontalScrollbarSize:10,horizontalSliderSize:typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:0,horizontalHasArrows:typeof e.horizontalHasArrows<"u"?e.horizontalHasArrows:!1,vertical:typeof e.vertical<"u"?e.vertical:1,verticalScrollbarSize:typeof e.verticalScrollbarSize<"u"?e.verticalScrollbarSize:10,verticalHasArrows:typeof e.verticalHasArrows<"u"?e.verticalHasArrows:!1,verticalSliderSize:typeof e.verticalSliderSize<"u"?e.verticalSliderSize:0,scrollByPage:typeof e.scrollByPage<"u"?e.scrollByPage:!1};return t.horizontalSliderSize=typeof e.horizontalSliderSize<"u"?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof e.verticalSliderSize<"u"?e.verticalSliderSize:t.verticalScrollbarSize,Re&&(t.className+=" mac"),t}var b=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event?this._event:(this._event=(e,t,i)=>{if(this._disposed)return q(()=>{});let s={fn:e,thisArgs:t};this._listeners.push(s);let r=q(()=>{let n=this._listeners.indexOf(s);n!==-1&&this._listeners.splice(n,1)});return i&&(Array.isArray(i)?i.push(r):i.add(r)),r},this._event)}fire(e){if(!this._disposed)switch(this._listeners.length){case 0:return;case 1:{let{fn:t,thisArgs:i}=this._listeners[0];t.call(i,e);return}default:{let t=this._listeners.slice();for(let{fn:i,thisArgs:s}of t)i.call(s,e)}}}dispose(){this._disposed||(this._disposed=!0,this._listeners.length=0)}},_e;(e=>{function t(n,o){return n(h=>o.fire(h))}e.forward=t;function i(n,o){return(h,l,a)=>n(d=>h.call(l,o(d)),void 0,a)}e.map=i;function s(...n){return(o,h,l)=>{let a=new Cs;for(let d of n)a.add(d(c=>o.call(h,c)));return l&&(Array.isArray(l)?l.push(a):l.add(a)),a}}e.any=s;function r(n,o){return o(void 0),n(h=>o(h))}e.runAndSubscribe=r})(_e||(_e={}));var is=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._bufferService=i,this._optionsService=o,this._renderService=h,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;let l=this._register(new qa({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:a=>ni(s.window,a)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{l.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new sh(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},l)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","overviewRuler"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(r.onProtocolChange(a=>{this._scrollableElement.updateOptions({handleMouseWheel:!(a&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{e.style.backgroundColor=n.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=n.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(q(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register(q(()=>this._styleElement.remove())),this._register(_e.runAndSubscribe(n.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .scrollbar > .slider {",` background: ${n.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider:hover {",` background: ${n.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider.active {",` background: ${n.colors.scrollbarSliderActiveBackground.css};`,"}"].join(` +`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._scrollableElement.onScroll(a=>this._handleScroll(a))),this._register(En.addTarget(t)),this._register(B(t,Ce.Change,a=>this._handleGestureChange(a)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){!this._renderService||this._isSyncing||(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;i!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}_handleGestureChange(e){e.preventDefault(),e.stopPropagation();let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e.translationY})}};is=K([w(2,ce),w(3,Ne),w(4,Jr),w(5,lt),w(6,he),w(7,Fe)],is);var ss=class extends I{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(n=>this._removeDecoration(n))),this._register(q(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,i.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;let i=e.options.x??0;(e.options.anchor||"left")==="right"?t.style.right=i?`${i*this._renderService.dimensions.css.cell.width}px`:"":t.style.left=i?`${i*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};ss=K([w(1,ce),w(2,Ne),w(3,At),w(4,Fe)],ss);var nh=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(let t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position)){this._addLineToZone(t,e.marker.line);return}}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}},Be={full:0,left:0,center:0,right:0},qe={full:0,left:0,center:0,right:0},pt={full:0,left:0,center:0,right:0},oi=class extends I{constructor(e,t,i,s,r,n,o,h){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=o,this._coreBrowserService=h,this._colorZoneStore=new nh,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(q(()=>this._canvas?.remove()));let l=this._canvas.getContext("2d");if(l)this._ctx=l;else throw new Error("Ctx cannot be null");this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onRender(()=>{(!this._containerHeight||this._containerHeight!==this._screenElement.clientHeight)&&(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("overviewRuler",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._queueRefresh(!0)}get _width(){return this._optionsService.options.overviewRuler?.width||0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);qe.full=this._canvas.width,qe.left=e,qe.center=t,qe.right=e,this._refreshDrawHeightConstants(),pt.full=1,pt.left=1,pt.center=1+qe.left,pt.right=1+qe.left+qe.center}_refreshDrawHeightConstants(){Be.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);Be.left=t,Be.center=t,Be.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*Be.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let t of this._decorationService.decorations)this._colorZoneStore.addDecoration(t);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!=="full"&&this._renderColorZone(t);for(let t of e)t.position==="full"&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(pt[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-Be[e.position||"full"]/2),qe[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+Be[e.position||"full"]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};oi=K([w(2,ce),w(3,At),w(4,Fe),w(5,he),w(6,lt),w(7,Ne)],oi);var rs=class{constructor(e,t,i,s,r,n){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=n,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(e.keyCode===20||e.keyCode===229||e.keyCode===16||e.keyCode===17||e.keyCode===18)return!1;this._finalizeComposition(!1)}return e.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){let t={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let i;t.start+=this._dataAlreadySent.length,this._isComposing?i=this._textarea.value.substring(t.start,this._compositionPosition.start):i=this._textarea.value.substring(t.start),i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;let t=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(t,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;let e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){let t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};rs=K([w(2,ce),w(3,he),w(4,Xe),w(5,Fe)],rs);var ee=0,te=0,ie=0,$=0,ur={css:"#00000000",rgba:0},V;(e=>{function t(r,n,o,h){return h!==void 0?`#${Je(r)}${Je(n)}${Je(o)}${Je(h)}`:`#${Je(r)}${Je(n)}${Je(o)}`}e.toCss=t;function i(r,n,o,h=255){return(r<<24|n<<16|o<<8|h)>>>0}e.toRgba=i;function s(r,n,o,h){return{css:e.toCss(r,n,o,h),rgba:e.toRgba(r,n,o,h)}}e.toColor=s})(V||(V={}));var N;(e=>{function t(l,a){if($=(a.rgba&255)/255,$===1)return{css:a.css,rgba:a.rgba};let d=a.rgba>>24&255,c=a.rgba>>16&255,u=a.rgba>>8&255,f=l.rgba>>24&255,_=l.rgba>>16&255,p=l.rgba>>8&255;ee=f+Math.round((d-f)*$),te=_+Math.round((c-_)*$),ie=p+Math.round((u-p)*$);let y=V.toCss(ee,te,ie),E=V.toRgba(ee,te,ie);return{css:y,rgba:E}}e.blend=t;function i(l){return(l.rgba&255)===255}e.isOpaque=i;function s(l,a,d){let c=Jt.ensureContrastRatio(l.rgba,a.rgba,d);if(c)return V.toColor(c>>24&255,c>>16&255,c>>8&255)}e.ensureContrastRatio=s;function r(l){let a=(l.rgba|255)>>>0;return[ee,te,ie]=Jt.toChannels(a),{css:V.toCss(ee,te,ie),rgba:a}}e.opaque=r;function n(l,a){return $=Math.round(a*255),[ee,te,ie]=Jt.toChannels(l.rgba),{css:V.toCss(ee,te,ie,$),rgba:V.toRgba(ee,te,ie,$)}}e.opacity=n;function o(l,a){return $=l.rgba&255,n(l,$*a/255)}e.multiplyOpacity=o;function h(l){return[l.rgba>>24&255,l.rgba>>16&255,l.rgba>>8&255]}e.toColorRGB=h})(N||(N={}));var z;(e=>{let t,i;try{let r=document.createElement("canvas");r.width=1,r.height=1;let n=r.getContext("2d",{willReadFrequently:!0});n&&(t=n,t.globalCompositeOperation="copy",i=t.createLinearGradient(0,0,1,1))}catch{}function s(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),V.toColor(ee,te,ie);case 5:return ee=parseInt(r.slice(1,2).repeat(2),16),te=parseInt(r.slice(2,3).repeat(2),16),ie=parseInt(r.slice(3,4).repeat(2),16),$=parseInt(r.slice(4,5).repeat(2),16),V.toColor(ee,te,ie,$);case 7:return{css:r,rgba:(parseInt(r.slice(1),16)<<8|255)>>>0};case 9:return{css:r,rgba:parseInt(r.slice(1),16)>>>0}}let n=r.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(n)return ee=parseInt(n[1]),te=parseInt(n[2]),ie=parseInt(n[3]),$=Math.round((n[5]===void 0?1:parseFloat(n[5]))*255),V.toColor(ee,te,ie,$);if(r==="transparent")return{css:"transparent",rgba:0};if(!t||!i)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=i,t.fillStyle=r,typeof t.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[ee,te,ie,$]=t.getImageData(0,0,1,1).data,$!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:V.toRgba(ee,te,ie,$),css:r}}e.toColor=s})(z||(z={}));var oe;(e=>{function t(s){return i(s>>16&255,s>>8&255,s&255)}e.relativeLuminance=t;function i(s,r,n){let o=s/255,h=r/255,l=n/255,a=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),d=h<=.03928?h/12.92:Math.pow((h+.055)/1.055,2.4),c=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4);return a*.2126+d*.7152+c*.0722}e.relativeLuminance2=i})(oe||(oe={}));var Jt;(e=>{function t(o,h){if($=(h&255)/255,$===1)return h;let l=h>>24&255,a=h>>16&255,d=h>>8&255,c=o>>24&255,u=o>>16&255,f=o>>8&255;return ee=c+Math.round((l-c)*$),te=u+Math.round((a-u)*$),ie=f+Math.round((d-f)*$),V.toRgba(ee,te,ie)}e.blend=t;function i(o,h,l){let a=oe.relativeLuminance(o>>8),d=oe.relativeLuminance(h>>8);if(Te(a,d)>8));if(_>8));return _>y?f:p}return f}let c=r(o,h,l),u=Te(a,oe.relativeLuminance(c>>8));if(u>8));return u>_?c:f}return c}}e.ensureContrastRatio=i;function s(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Te(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p0||f>0||_>0);)u-=Math.max(0,Math.ceil(u*.1)),f-=Math.max(0,Math.ceil(f*.1)),_-=Math.max(0,Math.ceil(_*.1)),p=Te(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));return(u<<24|f<<16|_<<8|255)>>>0}e.reduceLuminance=s;function r(o,h,l){let a=o>>24&255,d=o>>16&255,c=o>>8&255,u=h>>24&255,f=h>>16&255,_=h>>8&255,p=Te(oe.relativeLuminance2(u,f,_),oe.relativeLuminance2(a,d,c));for(;p>>0}e.increaseLuminance=r;function n(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}e.toChannels=n})(Jt||(Jt={}));function Je(e){let t=e.toString(16);return t.length<2?"0"+t:t}function Te(e,t){return e1){let d=this._getJoinedRanges(s,o,n,t,r);for(let c=0;c1){let a=this._getJoinedRanges(s,o,n,t,r);for(let d=0;d=Nt,k=m,v=this._workCell;if(u.length>0&&m===u[0][0]&&S){let T=u.shift(),gi=this._isCellInSelection(T[0],t);for(A=T[0]+1;A=T[1]),S?(C=!0,v=new oh(this._workCell,e.translateToString(!0,T[0],T[1]),T[1]-T[0]),k=T[1]-1,g=v.getWidth()):Nt=T[1]}let M=this._isCellInSelection(m,t),H=i&&m===n,ne=pi&&m>=a&&m<=d,Ee=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{Ee=!0});let Pe=v.getChars()||" ";if(Pe===" "&&(v.isUnderline()||v.isOverline())&&(Pe="\xA0"),xe=g*h-l.get(Pe,v.isBold(),v.isItalic()),!p)p=this._document.createElement("span");else if(y&&(M&&ye||!M&&!ye&&v.bg===R)&&(M&&ye&&f.selectionForeground||v.fg===x)&&v.extended.ext===L&&ne===P&&xe===re&&!H&&!C&&!Ee&&S){v.isInvisible()?E+=" ":E+=Pe,y++;continue}else y&&(p.textContent=E),p=this._document.createElement("span"),y=0,E="";if(R=v.bg,x=v.fg,L=v.extended.ext,P=ne,re=xe,ye=M,C&&n>=m&&n<=k&&(n=m),!this._coreService.isCursorHidden&&H&&this._coreService.isCursorInitialized){if(F.push("xterm-cursor"),this._coreBrowserService.isFocused)o&&F.push("xterm-cursor-blink"),F.push(s==="bar"?"xterm-cursor-bar":s==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":F.push("xterm-cursor-outline");break;case"block":F.push("xterm-cursor-block");break;case"bar":F.push("xterm-cursor-bar");break;case"underline":F.push("xterm-cursor-underline");break;default:break}}if(v.isBold()&&F.push("xterm-bold"),v.isItalic()&&F.push("xterm-italic"),v.isDim()&&F.push("xterm-dim"),v.isInvisible()?E=" ":E=v.getChars()||" ",v.isUnderline()&&(F.push(`xterm-underline-${v.extended.underlineStyle}`),E===" "&&(E="\xA0"),!v.isUnderlineColorDefault()))if(v.isUnderlineColorRGB())p.style.textDecorationColor=`rgb(${Tt.toColorRGB(v.getUnderlineColor()).join(",")})`;else{let T=v.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&v.isBold()&&T<8&&(T+=8),p.style.textDecorationColor=f.ansi[T].css}v.isOverline()&&(F.push("xterm-overline"),E===" "&&(E="\xA0")),v.isStrikethrough()&&F.push("xterm-strikethrough"),ne&&(p.style.textDecoration="underline");let J=v.getFgColor(),We=v.getFgColorMode(),le=v.getBgColor(),Ge=v.getBgColorMode(),ut=!!v.isInverse();if(ut){let T=J;J=le,le=T;let gi=We;We=Ge,Ge=gi}let ze,Ft,_t=!1;this._decorationService.forEachDecorationAtCell(m,t,void 0,T=>{T.options.layer!=="top"&&_t||(T.backgroundColorRGB&&(Ge=50331648,le=T.backgroundColorRGB.rgba>>8&16777215,ze=T.backgroundColorRGB),T.foregroundColorRGB&&(We=50331648,J=T.foregroundColorRGB.rgba>>8&16777215,Ft=T.foregroundColorRGB),_t=T.options.layer==="top")}),!_t&&M&&(ze=this._coreBrowserService.isFocused?f.selectionBackgroundOpaque:f.selectionInactiveBackgroundOpaque,le=ze.rgba>>8&16777215,Ge=50331648,_t=!0,f.selectionForeground&&(We=50331648,J=f.selectionForeground.rgba>>8&16777215,Ft=f.selectionForeground)),_t&&F.push("xterm-decoration-top");let Ke;switch(Ge){case 16777216:case 33554432:Ke=f.ansi[le],F.push(`xterm-bg-${le}`);break;case 50331648:Ke=V.toColor(le>>16,le>>8&255,le&255),this._addStyle(p,`background-color:#${(le>>>0).toString(16).padStart(6,"0")}`);break;default:ut?(Ke=f.foreground,F.push("xterm-bg-257")):Ke=f.background}switch(ze||v.isDim()&&(ze=N.multiplyOpacity(Ke,.5)),We){case 16777216:case 33554432:v.isBold()&&J<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(J+=8),this._applyMinimumContrast(p,Ke,f.ansi[J],v,ze,void 0)||F.push(`xterm-fg-${J}`);break;case 50331648:let T=V.toColor(J>>16&255,J>>8&255,J&255);this._applyMinimumContrast(p,Ke,T,v,ze,Ft)||this._addStyle(p,`color:#${J.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(p,Ke,f.foreground,v,ze,Ft)||ut&&F.push("xterm-fg-257")}F.length&&(p.className=F.join(" "),F.length=0),!H&&!C&&!Ee&&S?y++:p.textContent=E,xe!==this.defaultSpacing&&(p.style.letterSpacing=`${xe}px`),c.push(p),m=k}return p&&y&&(p.textContent=E),c}_applyMinimumContrast(e,t,i,s,r,n){if(this._optionsService.rawOptions.minimumContrastRatio===1||lh(s.getCode()))return!1;let o=this._getContrastCache(s),h;if(!r&&!n&&(h=o.getColor(t.rgba,i.rgba)),h===void 0){let l=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);h=N.ensureContrastRatio(r??t,n??i,l),o.setColor((r??t).rgba,(n??i).rgba,h??null)}return h?(this._addStyle(e,`color:${h.css}`),!0):!1}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){let i=this._selectionStart,s=this._selectionEnd;return!i||!s?!1:this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0]}};ns=K([w(1,en),w(2,he),w(3,Ne),w(4,Xe),w(5,At),w(6,lt)],ns);var dh=class{constructor(e=()=>new uh){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s=0;if(!t&&!i&&e.length===1&&(s=e.charCodeAt(0))<256){if(this._flat[s]!==-9999)return this._flat[s];let o=this._measure(e,0);return o>0&&(this._flat[s]=o),o}let r=e;t&&(r+="B"),i&&(r+="I");let n=this._holey.get(r);if(n===void 0){let o=0;t&&(o|=1),i&&(o|=2),n=this._measure(e,o),n>0&&this._holey.set(r,n)}return n}_measure(e,t){return this._canvasElements[t].measure(e)}},uh=class{constructor(){typeof OffscreenCanvas<"u"?(this._canvas=new OffscreenCanvas(1,1),this._ctx=_r(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=_r(this._canvas.getContext("2d")))}setFont(e,t,i,s){let r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}},_h=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1]){this.clear();return}let r=e.buffers.active.ydisp,n=t[1]-r,o=i[1]-r,h=Math.max(n,0),l=Math.min(o,e.rows-1);if(h>=e.rows||l<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=n,this.viewportEndRow=o,this.viewportCappedStartRow=h,this.viewportCappedEndRow=l,this.startCol=t[0],this.endCol=i[0]}isCellSelected(e,t,i){return this.hasSelection?(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol):!1}};function fh(){return new _h}var xi="xterm-dom-renderer-owner-",pe="xterm-rows",zt="xterm-fg-",fr="xterm-bg-",gt="xterm-focus",Kt="xterm-selection",Zt="xterm-cursor-blink-idle",ph=1,os=class extends I{constructor(e,t,i,s,r,n,o,h,l,a,d,c,u,f){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=n,this._linkifier2=o,this._charSizeService=l,this._optionsService=a,this._bufferService=d,this._coreService=c,this._coreBrowserService=u,this._themeService=f,this._terminalClass=ph++,this._rowElements=[],this._selectionRenderModel=fh(),this.onRequestRedraw=this._register(new b).event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(pe),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add(Kt),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=ch(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(_=>this._injectCss(_))),this._injectCss(this._themeService.colors),this._rowFactory=h.createInstance(ns,document),this._element.classList.add(xi+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(_=>this._handleLinkHover(_))),this._register(this._linkifier2.onHideLinkUnderline(_=>this._handleLinkLeave(_))),this._cursorBlinkStateManager=new gh(this._rowContainer,this._coreBrowserService),this._register(B(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register(q(()=>this._cursorBlinkStateManager.dispose())),this._register(q(()=>{this._element.classList.remove(xi+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new dh,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let i of this._rowElements)i.style.width=`${this.dimensions.css.canvas.width}px`,i.style.height=`${this.dimensions.css.cell.height}px`,i.style.lineHeight=`${this.dimensions.css.cell.height}px`,i.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .${pe} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${pe} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${pe} .xterm-dim { color: ${N.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;let i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${pe}.${gt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${pe}.${gt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .${pe}.${gt} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${pe}.${Zt} .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${pe} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${Kt} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Kt} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Kt} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[n,o]of e.ansi.entries())t+=`${this._terminalSelector} .${zt}${n} { color: ${o.css}; }${this._terminalSelector} .${zt}${n}.xterm-dim { color: ${N.multiplyOpacity(o,.5).css}; }${this._terminalSelector} .${fr}${n} { background-color: ${o.css}; }`;t+=`${this._terminalSelector} .${zt}257 { color: ${N.opaque(e.background).css}; }${this._terminalSelector} .${zt}257.xterm-dim { color: ${N.multiplyOpacity(N.opaque(e.background),.5).css}; }${this._terminalSelector} .${fr}257 { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let i=this._rowElements.length;i<=t;i++){let s=this._document.createElement("div");this._rowContainer.appendChild(s),this._rowElements.push(s)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(gt),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(gt),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,i){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i),this.renderRows(0,this._bufferService.rows-1),!e||!t||(this._selectionRenderModel.update(this._terminal,e,t,i),!this._selectionRenderModel.hasSelection))return;let s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,n=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,h=this._document.createDocumentFragment();if(i){let l=e[0]>t[0];h.appendChild(this._createSelectionElement(n,l?t[0]:e[0],l?e[0]:t[0],o-n+1))}else{let l=s===n?e[0]:0,a=n===r?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(n,l,a));let d=o-n-1;if(h.appendChild(this._createSelectionElement(n+1,0,this._bufferService.cols,d)),n!==o){let c=r===o?t[0]:this._bufferService.cols;h.appendChild(this._createSelectionElement(o,0,c))}}this._selectionContainer.appendChild(h)}_createSelectionElement(e,t,i,s=1){let r=this._document.createElement("div"),n=t*this.dimensions.css.cell.width,o=this.dimensions.css.cell.width*(i-t);return n+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-n),r.style.height=`${s*this.dimensions.css.cell.height}px`,r.style.top=`${e*this.dimensions.css.cell.height}px`,r.style.left=`${n}px`,r.style.width=`${o}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren()}renderRows(e,t){let i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),n=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,h=this._optionsService.rawOptions.cursorInactiveStyle;for(let l=e;l<=t;l++){let a=l+i.ydisp,d=this._rowElements[l],c=i.lines.get(a);if(!d||!c)break;d.replaceChildren(...this._rowFactory.createRow(c,a,a===s,o,h,r,n,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${xi}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,n){i<0&&(e=0),s<0&&(t=0);let o=this._bufferService.rows-1;i=Math.max(Math.min(i,o),0),s=Math.max(Math.min(s,o),0),r=Math.min(r,this._bufferService.cols);let h=this._bufferService.buffer,l=h.ybase+h.y,a=Math.min(h.x,r-1),d=this._optionsService.rawOptions.cursorBlink,c=this._optionsService.rawOptions.cursorStyle,u=this._optionsService.rawOptions.cursorInactiveStyle;for(let f=i;f<=s;++f){let _=f+h.ydisp,p=this._rowElements[f],y=h.lines.get(_);if(!p||!y)break;p.replaceChildren(...this._rowFactory.createRow(y,_,_===l,c,u,a,d,this.dimensions.css.cell.width,this._widthCache,n?f===i?e:0:-1,n?(f===s?t:r)-1:-1))}}};os=K([w(7,bs),w(8,ci),w(9,he),w(10,ce),w(11,Xe),w(12,Ne),w(13,lt)],os);var gh=class{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove(Zt),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove(Zt),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add(Zt),this._isIdlePaused=!0,this._idleTimeout=void 0}},as=class extends I{constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new b),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new mh(this._optionsService))}catch{this._measureStrategy=this._register(new vh(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};as=K([w(2,he)],as);var Rn=class extends I{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},vh=class extends Rn{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},mh=class extends Rn{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}},Sh=class extends I{constructor(e,t,i){super(),this._textarea=e,this._window=t,this.mainDocument=i,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new wh(this._window)),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new b),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange(s=>this._screenDprMonitor.setWindow(s))),this._register(_e.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(B(this._textarea,"focus",()=>this._isFocused=!0)),this._register(B(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},wh=class extends I{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new at),this._onDprChange=this._register(new b),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(q(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=B(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}},bh=class extends I{constructor(){super(),this.linkProviders=[],this._register(q(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function Ps(e,t,i){let s=i.getBoundingClientRect(),r=e.getComputedStyle(i),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-s.left-n,t.clientY-s.top-o]}function yh(e,t,i,s,r,n,o,h,l){if(!n)return;let a=Ps(e,t,i);if(a)return a[0]=Math.ceil((a[0]+(l?o/2:0))/o),a[1]=Math.ceil(a[1]/h),a[0]=Math.min(Math.max(a[0],1),s+(l?1:0)),a[1]=Math.min(Math.max(a[1],1),r),a}var hs=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,i,s,r){return yh(window,e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){let i=Ps(be(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};hs=K([w(0,Fe),w(1,ci)],hs);var Ch=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh())),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,e):e,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,t):t,!this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}},Mn={};so(Mn,{getSafariVersion:()=>xh,isChromeOS:()=>An,isFirefox:()=>Ln,isLegacyEdge:()=>kh,isLinux:()=>As,isMac:()=>Rt,isNode:()=>ui,isSafari:()=>Pn,isWindows:()=>Tn});var ui=!!(typeof process<"u"&&"title"in process&&(typeof navigator>"u"||navigator.userAgent.startsWith("Node.js/"))),Ot=ui?"node":navigator.userAgent,Ts=ui?"node":navigator.platform,Ln=Ot.includes("Firefox"),kh=Ot.includes("Edge"),Pn=/^((?!chrome|android).)*safari/i.test(Ot);function xh(){if(!Pn)return 0;let e=Ot.match(/Version\/(\d+)/);return e===null||e.length<2?0:parseInt(e[1])}var Rt=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(Ts),Tn=["Windows","Win16","Win32","WinCE"].includes(Ts),As=Ts.indexOf("Linux")>=0,An=/\bCrOS\b/.test(Ot),On=class{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ir){s-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),this._start();return}s=r}this.clear()}},Eh=class extends On{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},Bh=class extends On{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},hi=!ui&&"requestIdleCallback"in window?Bh:Eh,Dh=class{constructor(){this._queue=new hi}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}},ls=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._rowCount=e,this._optionsService=i,this._charSizeService=s,this._coreService=r,this._coreBrowserService=h,this._renderer=this._register(new at),this._pausedResizeTask=new Dh,this._observerDisposable=this._register(new at),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new b),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new b),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new Ch((a,d)=>this._renderRows(a,d),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new Rh(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(q(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(o.onResize(()=>this._fullRefresh())),this._register(o.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(n.onDecorationRegistered(()=>this._fullRefresh())),this._register(n.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(o.cols,o.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(o.buffer.y,o.buffer.y,void 0,!0))),this._register(l.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(a=>this._registerIntersectionObserver(a,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){let i=new e.IntersectionObserver(s=>this._handleIntersectionChange(s[s.length-1]),{threshold:0});i.observe(t),this._observerDisposable.value=q(()=>i.disconnect())}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(t=>this.refreshRows(t.start,t.end,t.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};ls=K([w(2,he),w(3,ci),w(4,Xe),w(5,At),w(6,ce),w(7,Ne),w(8,lt)],ls);var Rh=class{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??(this._timeout=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3))}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Mh(e,t,i,s){let r=i.buffer.x,n=i.buffer.y;if(!i.buffer.hasScrollback)return Th(r,n,e,t,i,s)+_i(n,t,i,s)+Ah(r,n,e,t,i,s);let o;if(n===t)return o=r>e?"D":"C",Lt(Math.abs(r-e),Mt(o,s));o=n>t?"D":"C";let h=Math.abs(n-t),l=Ph(n>t?e:r,i)+(h-1)*i.cols+1+Lh(n>t?r:e,i);return Lt(l,Mt(o,s))}function Lh(e,t){return e-1}function Ph(e,t){return t.cols-e}function Th(e,t,i,s,r,n){return _i(t,s,r,n).length===0?"":Lt(Nn(e,t,e,t-st(t,r),!1,r).length,Mt("D",n))}function _i(e,t,i,s){let r=e-st(e,i),n=t-st(t,i),o=Math.abs(r-n)-Oh(e,t,i);return Lt(o,Mt(In(e,t),s))}function Ah(e,t,i,s,r,n){let o;_i(t,s,r,n).length>0?o=s-st(s,r):o=t;let h=s,l=Ih(e,t,i,s,r,n);return Lt(Nn(e,o,i,h,l==="C",r).length,Mt(l,n))}function Oh(e,t,i){let s=0,r=e-st(e,i),n=t-st(t,i);for(let o=0;o=0&&e0?o=s-st(s,r):o=t,e=i&&ot?"A":"B"}function Nn(e,t,i,s,r,n){let o=e,h=t,l="";for(;(o!==i||h!==s)&&h>=0&&hn.cols-1?(l+=n.buffer.translateBufferLineToString(h,!1,e,o),o=0,e=0,h++):!r&&o<0&&(l+=n.buffer.translateBufferLineToString(h,!1,0,e+1),o=n.cols-1,e=o,h--);return l+n.buffer.translateBufferLineToString(h,!1,e,o)}function Mt(e,t){return"\x1B"+(t?"O":"[")+e}function Lt(e,t){e=Math.floor(e);let i="";for(let s=0;sthis._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let e=this.selectionStart,t=this.selectionEnd;return!e||!t?!1:e[1]>t[1]||e[1]===t[1]&&e[0]>t[0]}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function pr(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}var Ei=50,Fh=15,Hh=50,Wh=500,zh="\xA0",Kh=new RegExp(zh,"g"),cs=class extends I{constructor(e,t,i,s,r,n,o,h,l){super(),this._element=e,this._screenElement=t,this._linkifier=i,this._bufferService=s,this._coreService=r,this._mouseService=n,this._optionsService=o,this._renderService=h,this._coreBrowserService=l,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new ke,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new b),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new b),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new b),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=a=>this._handleMouseMove(a),this._mouseUpListener=a=>this._handleMouseUp(a),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(a=>this._handleTrim(a)),this._register(this._bufferService.buffers.onBufferActivate(a=>this._handleBufferActivate(a))),this.enable(),this._model=new Nh(this._bufferService),this._activeSelectionMode=0,this._register(q(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(a=>{a.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";let i=this._bufferService.buffer,s=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return"";let r=e[0]r.replace(Kh," ")).join(Tn?`\r `:` -`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),Ps&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s||!t?!1:this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){let i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s?!1:this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=_r(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let s=this._getMouseBufferCoords(e);return s?(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=Ms(this._coreBrowserService.window,e,this._screenElement)[1],i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-ki),ki),t/=ki,t/Math.abs(t)+Math.round(t*(Oh-1)))}shouldForceSelection(e){return Dt?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),Ih)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){let t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);let i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&i.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(Dt&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&tthis._handleTrim(t))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){let r=e.loadCell(s,this._workCell).getChars().length;this._workCell.getWidth()===0?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;let r=this._bufferService.buffer,n=r.lines.get(e[1]);if(!n)return;let o=r.translateBufferLineToString(e[1],!1),h=this._convertViewportColToCharacterIndex(n,e[0]),l=h,a=e[0]-h,d=0,c=0,u=0,f=0;if(o.charAt(h)===" "){for(;h>0&&o.charAt(h-1)===" ";)h--;for(;l1&&(f+=A-1,l+=A-1);y>0&&h>0&&!this._isCharWordSeparator(n.loadCell(y-1,this._workCell));){n.loadCell(y-1,this._workCell);let R=this._workCell.getChars().length;this._workCell.getWidth()===0?(d++,y--):R>1&&(u+=R-1,h-=R-1),h--,y--}for(;E1&&(f+=R-1,l+=R-1),l++,E++}}l++;let _=h+a-d+u,p=Math.min(this._bufferService.cols,l-h+d+c-u-f);if(!(!t&&o.slice(h,l).trim()==="")){if(i&&_===0&&n.getCodePoint(0)!==32){let y=r.lines.get(e[1]-1);if(y&&n.isWrapped&&y.getCodePoint(this._bufferService.cols-1)!==32){let E=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(E){let A=this._bufferService.cols-E.start;_-=A,p+=A}}}if(s&&_+p===this._bufferService.cols&&n.getCodePoint(this._bufferService.cols-1)!==32){let y=r.lines.get(e[1]+1);if(y?.isWrapped&&y.getCodePoint(0)!==32){let E=this._getWordAt([0,e[1]+1],!1,!1,!0);E&&(p+=E.length)}}return{start:_,length:p}}}_selectWordAt(e,t){let i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return e.getWidth()===0?!1:this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=_r(i,this._bufferService.cols)}};hs=K([w(3,ce),w(4,je),w(5,ws),w(6,he),w(7,Ne),w(8,Ie)],hs);var fr=class{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}},pr=class{constructor(){this._color=new fr,this._css=new fr}setCss(e,t,i){this._css.set(e,t,i)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,i){this._color.set(e,t,i)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}},Y=Object.freeze((()=>{let e=[z.toColor("#2e3436"),z.toColor("#cc0000"),z.toColor("#4e9a06"),z.toColor("#c4a000"),z.toColor("#3465a4"),z.toColor("#75507b"),z.toColor("#06989a"),z.toColor("#d3d7cf"),z.toColor("#555753"),z.toColor("#ef2929"),z.toColor("#8ae234"),z.toColor("#fce94f"),z.toColor("#729fcf"),z.toColor("#ad7fa8"),z.toColor("#34e2e2"),z.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){let s=t[i/36%6|0],r=t[i/6%6|0],n=t[i%6];e.push({css:V.toCss(s,r,n),rgba:V.toRgba(s,r,n)})}for(let i=0;i<24;i++){let s=8+i*10;e.push({css:V.toCss(s,s,s),rgba:V.toRgba(s,s,s)})}return e})()),Je=z.toColor("#ffffff"),yt=z.toColor("#000000"),gr=z.toColor("#ffffff"),vr=yt,gt={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},Wh=Je,ls=class extends I{constructor(e){super(),this._optionsService=e,this._contrastCache=new pr,this._halfContrastCache=new pr,this._onChangeColors=this._register(new b),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:Je,background:yt,cursor:gr,cursorAccent:vr,selectionForeground:void 0,selectionBackgroundTransparent:gt,selectionBackgroundOpaque:N.blend(yt,gt),selectionInactiveBackgroundTransparent:gt,selectionInactiveBackgroundOpaque:N.blend(yt,gt),scrollbarSliderBackground:N.opacity(Je,.2),scrollbarSliderHoverBackground:N.opacity(Je,.4),scrollbarSliderActiveBackground:N.opacity(Je,.5),overviewRulerBorder:Je,ansi:Y.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=O(e.foreground,Je),t.background=O(e.background,yt),t.cursor=N.blend(t.background,O(e.cursor,gr)),t.cursorAccent=N.blend(t.background,O(e.cursorAccent,vr)),t.selectionBackgroundTransparent=O(e.selectionBackground,gt),t.selectionBackgroundOpaque=N.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=O(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=N.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?O(e.selectionForeground,cr):void 0,t.selectionForeground===cr&&(t.selectionForeground=void 0),N.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=N.opacity(t.selectionBackgroundTransparent,.3)),N.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=N.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=O(e.scrollbarSliderBackground,N.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=O(e.scrollbarSliderHoverBackground,N.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=O(e.scrollbarSliderActiveBackground,N.opacity(t.foreground,.5)),t.overviewRulerBorder=O(e.overviewRulerBorder,Wh),t.ansi=Y.slice(),t.ansi[0]=O(e.black,Y[0]),t.ansi[1]=O(e.red,Y[1]),t.ansi[2]=O(e.green,Y[2]),t.ansi[3]=O(e.yellow,Y[3]),t.ansi[4]=O(e.blue,Y[4]),t.ansi[5]=O(e.magenta,Y[5]),t.ansi[6]=O(e.cyan,Y[6]),t.ansi[7]=O(e.white,Y[7]),t.ansi[8]=O(e.brightBlack,Y[8]),t.ansi[9]=O(e.brightRed,Y[9]),t.ansi[10]=O(e.brightGreen,Y[10]),t.ansi[11]=O(e.brightYellow,Y[11]),t.ansi[12]=O(e.brightBlue,Y[12]),t.ansi[13]=O(e.brightMagenta,Y[13]),t.ansi[14]=O(e.brightCyan,Y[14]),t.ansi[15]=O(e.brightWhite,Y[15]),e.extendedAnsi){let i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;s"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};function Kh(e,t,i,s){let r={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:e.key==="UIKeyInputUpArrow"?t?r.key="\x1BOA":r.key="\x1B[A":e.key==="UIKeyInputLeftArrow"?t?r.key="\x1BOD":r.key="\x1B[D":e.key==="UIKeyInputRightArrow"?t?r.key="\x1BOC":r.key="\x1B[C":e.key==="UIKeyInputDownArrow"&&(t?r.key="\x1BOB":r.key="\x1B[B");break;case 8:r.key=e.ctrlKey?"\b":"\x7F",e.altKey&&(r.key="\x1B"+r.key);break;case 9:if(e.shiftKey){r.key="\x1B[Z";break}r.key=" ",r.cancel=!0;break;case 13:r.key=e.altKey?"\x1B\r":"\r",r.cancel=!0;break;case 27:r.key="\x1B",e.altKey&&(r.key="\x1B\x1B"),r.cancel=!0;break;case 37:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"D":t?r.key="\x1BOD":r.key="\x1B[D";break;case 39:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"C":t?r.key="\x1BOC":r.key="\x1B[C";break;case 38:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"A":t?r.key="\x1BOA":r.key="\x1B[A";break;case 40:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"B":t?r.key="\x1BOB":r.key="\x1B[B";break;case 45:!e.shiftKey&&!e.ctrlKey&&(r.key="\x1B[2~");break;case 46:n?r.key="\x1B[3;"+(n+1)+"~":r.key="\x1B[3~";break;case 36:n?r.key="\x1B[1;"+(n+1)+"H":t?r.key="\x1BOH":r.key="\x1B[H";break;case 35:n?r.key="\x1B[1;"+(n+1)+"F":t?r.key="\x1BOF":r.key="\x1B[F";break;case 33:e.shiftKey?r.type=2:e.ctrlKey?r.key="\x1B[5;"+(n+1)+"~":r.key="\x1B[5~";break;case 34:e.shiftKey?r.type=3:e.ctrlKey?r.key="\x1B[6;"+(n+1)+"~":r.key="\x1B[6~";break;case 112:n?r.key="\x1B[1;"+(n+1)+"P":r.key="\x1BOP";break;case 113:n?r.key="\x1B[1;"+(n+1)+"Q":r.key="\x1BOQ";break;case 114:n?r.key="\x1B[1;"+(n+1)+"R":r.key="\x1BOR";break;case 115:n?r.key="\x1B[1;"+(n+1)+"S":r.key="\x1BOS";break;case 116:n?r.key="\x1B[15;"+(n+1)+"~":r.key="\x1B[15~";break;case 117:n?r.key="\x1B[17;"+(n+1)+"~":r.key="\x1B[17~";break;case 118:n?r.key="\x1B[18;"+(n+1)+"~":r.key="\x1B[18~";break;case 119:n?r.key="\x1B[19;"+(n+1)+"~":r.key="\x1B[19~";break;case 120:n?r.key="\x1B[20;"+(n+1)+"~":r.key="\x1B[20~";break;case 121:n?r.key="\x1B[21;"+(n+1)+"~":r.key="\x1B[21~";break;case 122:n?r.key="\x1B[23;"+(n+1)+"~":r.key="\x1B[23~";break;case 123:n?r.key="\x1B[24;"+(n+1)+"~":r.key="\x1B[24~";break;default:if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&!e.metaKey)e.keyCode>=65&&e.keyCode<=90?r.key=String.fromCharCode(e.keyCode-64):e.keyCode===32?r.key="\0":e.keyCode>=51&&e.keyCode<=55?r.key=String.fromCharCode(e.keyCode-51+27):e.keyCode===56?r.key="\x7F":e.key==="/"?r.key="":e.keyCode===219?r.key="\x1B":e.keyCode===220?r.key="":e.keyCode===221&&(r.key="");else if((!i||s)&&e.altKey&&!e.metaKey){let o=zh[e.keyCode]?.[e.shiftKey?1:0];if(o)r.key="\x1B"+o;else if(e.keyCode>=65&&e.keyCode<=90){let h=e.ctrlKey?e.keyCode-64:e.keyCode+32,l=String.fromCharCode(h);e.shiftKey&&(l=l.toUpperCase()),r.key="\x1B"+l}else if(e.keyCode===32)r.key="\x1B"+(e.ctrlKey?"\0":" ");else if(e.key==="Dead"&&e.code.startsWith("Key")){let h=e.code.slice(3,4);e.shiftKey||(h=h.toLowerCase()),r.key="\x1B"+h,r.cancel=!0}}else if(i&&!e.altKey&&!e.ctrlKey&&!e.shiftKey&&e.metaKey)e.keyCode===65&&(r.type=1);else if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&e.key.length===1)r.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":r.key="";break;case"Digit2":r.key="\0";break;case"Digit6":r.key="";break}break}return r}var mr=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){let t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e){let t=this._getNumpadKeyCode(e);if(t!==void 0)return t;let i=this._getModifierKeyCode(e);if(i!==void 0)return i;let s=this._functionalKeyCodes[e.key];if(s!==void 0)return s;if(e.shiftKey&&e.code){if(e.code.startsWith("Digit")&&e.code.length===6){let r=e.code.charAt(5);if(r>="0"&&r<="9")return r.charCodeAt(0)}if(e.code.startsWith("Key")&&e.code.length===4)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(e.key.length===1){let r=e.key.codePointAt(0);return r>=65&&r<=90?r+32:r}}_isModifierKey(e){return e.key==="Shift"||e.key==="Control"||e.key==="Alt"||e.key==="Meta"}_buildCsiLetterSequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1B["+e}_buildSs3Sequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1BO"+e}_buildCsiTildeSequence(e,t,i,s){let r=s&&i!==1,n="\x1B["+e;return(t>0||r)&&(n+=";"+(t>0?t:"1"),r&&(n+=":"+i)),n+="~",n}_buildCsiUSequence(e,t,i,s,r,n,o){let h=!!(r&2),l=!!(r&4),a="\x1B["+t,d;l&&e.shiftKey&&e.key.length===1&&!n&&!o&&(d=e.key.codePointAt(0),a+=":"+d);let c=r&16&&s!==3&&e.key.length===1&&!n&&!o&&!e.ctrlKey?e.key.codePointAt(0):void 0,u=h&&s!==1&&(s===3||c===void 0);return(i>0||u||c!==void 0)&&(a+=";",i>0?a+=i:u&&(a+="1"),u&&(a+=":"+s)),c!==void 0&&(a+=";"+c),a+="u",a}evaluate(e,t,i=1){let s={type:0,cancel:!1,key:void 0},r=this._encodeModifiers(e),n=this._isModifierKey(e),o=!!(t&2);if(!o&&i===3||n&&!(t&8)&&!o)return s;let h=this._csiLetterKeys[e.key];if(h)return s.key=this._buildCsiLetterSequence(h,r,i,o),s.cancel=!0,s;let l=this._ss3FunctionKeys[e.key];if(l)return s.key=this._buildSs3Sequence(l,r,i,o),s.cancel=!0,s;let a=this._csiTildeKeys[e.key];if(a!==void 0)return s.key=this._buildCsiTildeSequence(a,r,i,o),s.cancel=!0,s;let d=this._getKeyCode(e);if(d===void 0)return s;let c=this._functionalKeyCodes[e.key]!==void 0||this._getNumpadKeyCode(e)!==void 0,u=!1;return t&8||o?u=!0:t&1&&(d===27||d===127||d===13||d===9||d===32||c?u=!0:r>0&&(e.shiftKey&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1?u=!1:u=!0)),u?(s.key=this._buildCsiUSequence(e,d,r,i,t,c,n),s.cancel=!0):e.key.length===1&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&(s.key=e.key),s}static shouldUseProtocol(e){return e>0}},$h=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){let t=this._codeToVk[e.code];return t!==void 0?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(e.key==="Enter")return 10;if(e.key==="Backspace")return 127}let t=this._keyToControlChar[e.key];if(t!==void 0)return t;if(e.key.length===1){let i=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(i>=65&&i<=90)return i-64;if(i>=97&&i<=122)return i-96}return i}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&(e.code==="ControlRight"?t|=4:t|=8),e.altKey&&(e.code==="AltRight"?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){let i=this._getVirtualKeyCode(e),s=this._getScanCode(e),r=this._getUnicodeChar(e),n=t?1:0,o=this._getControlKeyState(e);return{type:0,cancel:!0,key:`\x1B[${i};${s};${r};${n};${o};1_`}}},cs=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??(this._win32InputMode=new $h),this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??(this._kittyKeyboard=new mr),this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);let t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1):Kh(e,this._coreService.decPrivateModes.applicationCursorKeys,Dt,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);let t=this._coreService.kittyKeyboard.flags;if(this.useKitty&&t&2)return this._getKittyKeyboard().evaluate(e,t,3)}get useKitty(){let e=this._coreService.kittyKeyboard.flags;return!!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard&&mr.shouldUseProtocol(e))}get useWin32InputMode(){return!!(this._optionsService.rawOptions.vtExtensions?.win32InputMode&&this._coreService.decPrivateModes.win32InputMode)}};cs=K([w(0,je),w(1,he)],cs);var Uh=class{constructor(...e){this._entries=new Map;for(let[t,i]of e)this.set(t,i)}set(e,t){let i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(let[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}},qh=class{constructor(){this._services=new Uh,this._services.set(Ss,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){let i=ao(e).sort((n,o)=>n.index-o.index),s=[];for(let n of i){let o=this._services.get(n.id);if(!o)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${n.id._id}.`);s.push(o)}let r=i.length>0?i[0].index:t.length;if(t.length!==r)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${r+1} conflicts with ${t.length} static arguments`);return new e(...t,...s)}},Vh={trace:0,debug:1,info:2,warn:3,error:4,off:5},Yh="xterm.js: ",ds=class extends I{constructor(e){super(),this._optionsService=e,this._logLevel=5,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=Vh[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;s--)this._array[this._getCyclicIndex(s+i.length)]=this._array[this._getCyclicIndex(s)];for(let s=0;sthis._maxLength){let s=this._length+i.length-this._maxLength;this._startIndex+=s,this._length=this._maxLength,this.onTrimEmitter.fire(s)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let r=t-1;r>=0;r--)this.set(e+r+i,this.get(e+r));let s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>22,i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):s]}set(t,i){this._data[t*D+1]=i[0],i[1].length>1?(this._combined[t]=i[1],this._data[t*D+0]=t|2097152|i[2]<<22):this._data[t*D+0]=i[1].charCodeAt(0)|i[2]<<22}getWidth(t){return this._data[t*D+0]>>22}hasWidth(t){return this._data[t*D+0]&12582912}getFg(t){return this._data[t*D+1]}getBg(t){return this._data[t*D+2]}hasContent(t){return this._data[t*D+0]&4194303}getCodePoint(t){let i=this._data[t*D+0];return i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):i&2097151}isCombined(t){return this._data[t*D+0]&2097152}getString(t){let i=this._data[t*D+0];return i&2097152?this._combined[t]:i&2097151?Ve(i&2097151):""}isProtected(t){return this._data[t*D+2]&536870912}loadCell(t,i){return zt=t*D,i.content=this._data[zt+0],i.fg=this._data[zt+1],i.bg=this._data[zt+2],i.content&2097152&&(i.combinedData=this._combined[t]),i.bg&268435456&&(i.extended=this._extendedAttrs[t]),i}setCell(t,i){i.content&2097152&&(this._combined[t]=i.combinedData),i.bg&268435456&&(this._extendedAttrs[t]=i.extended),this._data[t*D+0]=i.content,this._data[t*D+1]=i.fg,this._data[t*D+2]=i.bg}setCellFromCodepoint(t,i,s,r){r.bg&268435456&&(this._extendedAttrs[t]=r.extended),this._data[t*D+0]=i|s<<22,this._data[t*D+1]=r.fg,this._data[t*D+2]=r.bg}addCodepointToCell(t,i,s){let r=this._data[t*D+0];r&2097152?this._combined[t]+=Ve(i):r&2097151?(this._combined[t]=Ve(r&2097151)+Ve(i),r&=-2097152,r|=2097152):r=i|1<<22,s&&(r&=-12582913,r|=s<<22),this._data[t*D+0]=r}insertCells(t,i,s){if(t%=this.length,t&&this.getWidth(t-1)===2&&this.setCellFromCodepoint(t-1,0,1,s),i=0;--r)this.setCell(t+i+r,this.loadCell(t+r,wr));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=s*4)this._data=new Uint32Array(this._data.buffer,0,s);else{let r=new Uint32Array(s);r.set(this._data),this._data=r}for(let r=this.length;r=t&&delete this._combined[h]}let n=Object.keys(this._extendedAttrs);for(let o=0;o=t&&delete this._extendedAttrs[h]}}return this.length=t,s*4*xi=0;--t)if(this._data[t*D+0]&4194303)return t+(this._data[t*D+0]>>22);return 0}getNoBgTrimmedLength(){for(let t=this.length-1;t>=0;--t)if(this._data[t*D+0]&4194303||this._data[t*D+2]&50331648)return t+(this._data[t*D+0]>>22);return 0}copyCellsFrom(t,i,s,r,n){let o=t._data;if(n)for(let l=r-1;l>=0;l--){for(let a=0;a=i&&(this._combined[a-i+s]=t._combined[a])}}translateToString(t,i,s,r){i=i??0,s=s??this.length,t&&(s=Math.min(s,this.getTrimmedLength())),r&&(r.length=0);let n="";for(;i>22||1}return r&&r.push(i),n}};function jh(e,t,i,s,r,n){let o=[];for(let h=0;h=h&&s0&&(y>c||d[y].getTrimmedLength()===0);y--)p++;p>0&&(o.push(h+d.length-p),o.push(p)),h+=d.length-1}return o}function Xh(e,t){let i=[],s=0,r=t[s],n=0;for(let o=0;oLt(e,a,t)).reduce((l,a)=>l+a),n=0,o=0,h=0;for(;hl&&(n-=l,o++);let a=e[o].getWidth(n-1)===2;a&&n--;let d=a?i-1:i;s.push(d),h+=d}return s}function Lt(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();let s=!e[t].hasContent(i-1)&&e[t].getWidth(i-1)===1,r=e[t+1].getWidth(0)===2;return s&&r?i-1:i}var Nn=class Fn{constructor(t){this.line=t,this.isDisposed=!1,this._disposables=[],this._id=Fn._nextId++,this._onDispose=this.register(new b),this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Ii(this._disposables),this._disposables.length=0)}register(t){return this._disposables.push(t),t}};Nn._nextId=1;var Zh=Nn,X={},Ze=X.B;X[0]={"`":"\u25C6",a:"\u2592",b:"\u2409",c:"\u240C",d:"\u240D",e:"\u240A",f:"\xB0",g:"\xB1",h:"\u2424",i:"\u240B",j:"\u2518",k:"\u2510",l:"\u250C",m:"\u2514",n:"\u253C",o:"\u23BA",p:"\u23BB",q:"\u2500",r:"\u23BC",s:"\u23BD",t:"\u251C",u:"\u2524",v:"\u2534",w:"\u252C",x:"\u2502",y:"\u2264",z:"\u2265","{":"\u03C0","|":"\u2260","}":"\xA3","~":"\xB7"};X.A={"#":"\xA3"};X.B=void 0;X[4]={"#":"\xA3","@":"\xBE","[":"ij","\\":"\xBD","]":"|","{":"\xA8","|":"f","}":"\xBC","~":"\xB4"};X.C=X[5]={"[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X.R={"#":"\xA3","@":"\xE0","[":"\xB0","\\":"\xE7","]":"\xA7","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xA8"};X.Q={"@":"\xE0","[":"\xE2","\\":"\xE7","]":"\xEA","^":"\xEE","`":"\xF4","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xFB"};X.K={"@":"\xA7","[":"\xC4","\\":"\xD6","]":"\xDC","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xDF"};X.Y={"#":"\xA3","@":"\xA7","[":"\xB0","\\":"\xE7","]":"\xE9","`":"\xF9","{":"\xE0","|":"\xF2","}":"\xE8","~":"\xEC"};X.E=X[6]={"@":"\xC4","[":"\xC6","\\":"\xD8","]":"\xC5","^":"\xDC","`":"\xE4","{":"\xE6","|":"\xF8","}":"\xE5","~":"\xFC"};X.Z={"#":"\xA3","@":"\xA7","[":"\xA1","\\":"\xD1","]":"\xBF","{":"\xB0","|":"\xF1","}":"\xE7"};X.H=X[7]={"@":"\xC9","[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X["="]={"#":"\xF9","@":"\xE0","[":"\xE9","\\":"\xE7","]":"\xEA","^":"\xEE",_:"\xE8","`":"\xF4","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xFB"};var br=4294967295,yr=class{constructor(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=Q.clone(),this.savedCharset=Ze,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=ke.fromCharData([0,"",1,0]),this._whitespaceCell=ke.fromCharData([0," ",1,32]),this._isClearing=!1,this._memoryCleanupQueue=new oi,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new Sr(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new Qt),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new Qt),this._whitespaceCell}getBlankLine(e,t){return new Ct(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&ebr?br:t}fillViewportRows(e){if(this.lines.length===0){e??(e=Q);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new Sr(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let i=this.getNullCell(Q),s=0,r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+n+1?(this.ybase--,n++,this.ydisp>0&&this.ydisp--):this.lines.push(new Ct(e,i)));else for(let o=this._rows;o>t;o--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(o),this.ybase=Math.max(this.ybase-o,0),this.ydisp=Math.max(this.ydisp-o,0),this.savedY=Math.max(this.savedY-o,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),n&&(this.y+=n),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let n=0;n0){let n=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,n)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend==="conpty"&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let i=this._optionsService.rawOptions.reflowCursorLine,s=jh(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(Q),i);if(s.length>0){let r=Xh(this.lines,s);Gh(this.lines,r.layout),this._reflowLargerAdjustViewport(e,t,r.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){let s=this.getNullCell(Q),r=i;for(;r-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;o--){let h=this.lines.get(o);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;let l=[h];for(;h.isWrapped&&o>0;)h=this.lines.get(--o),l.unshift(h);if(!i){let R=this.ybase+this.y;if(R>=o&&R0&&(r.push({start:o+l.length+n,newLines:f}),n+=f.length),l.push(...f);let _=d.length-1,p=d[_];p===0&&(_--,p=d[_]);let y=l.length-c-1,E=a;for(;y>=0;){let R=Math.min(E,p);if(l[_]===void 0)break;if(l[_].copyCellsFrom(l[y],E-R,p-R,R,!0),p-=R,p===0&&(_--,p=d[_]),E-=R,E===0){y--;let x=Math.max(y,0);E=Lt(l,x,this._cols)}}for(let R=0;R0;)this.ybase===0?this.y0){let o=[],h=[];for(let p=0;p=0;p--)if(c&&c.start>a+u){for(let y=c.newLines.length-1;y>=0;y--)this.lines.set(p--,c.newLines[y]);p++,o.push({index:a+1,amount:c.newLines.length}),u+=c.newLines.length,c=r[++d]}else this.lines.set(p,h[a--]);let f=0;for(let p=o.length-1;p>=0;p--)o[p].index+=f,this.lines.onInsertEmitter.fire(o[p]),f+=o[p].amount;let _=Math.max(0,l+n-this.lines.maxLength);_>0&&this.lines.onTrimEmitter.fire(_)}}translateBufferLineToString(e,t,i=0,s){let r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=i,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(i=>{t.line>=i.index&&(t.line+=i.amount)})),t.register(this.lines.onDelete(i=>{t.line>=i.index&&t.linei.index&&(t.line-=i.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}},Qh=class extends I{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new b),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new yr(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new yr(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}},Hn=2,Wn=1,us=class extends I{constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,Hn),this.rows=Math.max(e.rawOptions.rows||0,Wn),this.buffers=this._register(new Qh(e,this)),this._register(this.buffers.onBufferActivate(t=>{this._onScroll.fire(t.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let i=this.buffer,s;s=this._cachedBlankLine,(!s||s.length!==this.cols||s.getFg(0)!==e.fg||s.getBg(0)!==e.bg)&&(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;let r=i.ybase+i.scrollTop,n=i.ybase+i.scrollBottom;if(i.scrollTop===0){let o=i.lines.isFull;n===i.lines.length-1?o?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(n+1,0,s.clone()),o?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{let o=n-r+1;i.lines.shiftElements(r+1,o-1,-1),i.lines.set(n,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){let i=this.buffer;if(e<0){if(i.ydisp===0)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);let s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};us=K([w(0,he)],us);var st={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:Dt,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",overviewRuler:{},quirks:{},vtExtensions:{}},el=["normal","bold","100","200","300","400","500","600","700","800","900"],tl=class extends I{constructor(e){super(),this._onOptionChange=this._register(new b),this.onOptionChange=this._onOptionChange.event;let t={...st};for(let i in e)if(i in t)try{let s=e[i];t[i]=this._sanitizeAndValidateOption(i,s)}catch(s){console.error(s)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(q(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{e.indexOf(i)!==-1&&t()})}_setupOptions(){let e=i=>{if(!(i in st))throw new Error(`No option with key "${i}"`);return this.rawOptions[i]},t=(i,s)=>{if(!(i in st))throw new Error(`No option with key "${i}"`);s=this._sanitizeAndValidateOption(i,s),this.rawOptions[i]!==s&&(this.rawOptions[i]=s,this._onOptionChange.fire(i))};for(let i in this.rawOptions){let s={get:e.bind(this,i),set:t.bind(this,i)};Object.defineProperty(this.options,i,s)}}_sanitizeAndValidateOption(e,t){switch(e){case"cursorStyle":if(t||(t=st[e]),!il(t))throw new Error(`"${t}" is not a valid value for ${e}`);break;case"wordSeparator":t||(t=st[e]);break;case"fontWeight":case"fontWeightBold":if(typeof t=="number"&&1<=t&&t<=1e3)break;t=el.includes(t)?t:st[e];break;case"cursorWidth":t=Math.floor(t);case"lineHeight":case"tabStopWidth":if(t<1)throw new Error(`${e} cannot be less than 1, value: ${t}`);break;case"minimumContrastRatio":t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case"scrollback":if(t=Math.min(t,4294967295),t<0)throw new Error(`${e} cannot be less than 0, value: ${t}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(t<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case"rows":case"cols":if(!t&&t!==0)throw new Error(`${e} must be numeric, value: ${t}`);break;case"windowsPty":t=t??{};break}return t}};function il(e){return e==="block"||e==="underline"||e==="bar"}function kt(e,t=5){if(typeof e!="object")return e;let i=Array.isArray(e)?[]:{};for(let s in e)i[s]=t<=1?e[s]:e[s]&&kt(e[s],t-1);return i}var Cr=Object.freeze({insertMode:!1}),kr=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0}),xr=()=>({flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}),_s=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new b),this.onData=this._onData.event,this._onUserInput=this._register(new b),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new b),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=kt(Cr),this.decPrivateModes=kt(kr),this.kittyKeyboard=xr()}reset(){this.modes=kt(Cr),this.decPrivateModes=kt(kr),this.kittyKeyboard=xr()}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(s=>s.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(t=>t.charCodeAt(0))),this._onBinary.fire(e))}};_s=K([w(0,ce),w(1,Gr),w(2,he)],_s);var Er={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>e.button===4||e.action!==1?!1:(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>e.action!==32},DRAG:{events:23,restrict:e=>!(e.action===32&&e.button===3)},ANY:{events:31,restrict:e=>!0}};function Ei(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return e.button===4?(i|=64,i|=e.action):(i|=e.button&3,e.button&4&&(i|=64),e.button&8&&(i|=128),e.action===32?i|=32:e.action===0&&!t&&(i|=3)),i}var Bi=String.fromCharCode,Br={DEFAULT:e=>{let t=[Ei(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`\x1B[M${Bi(t[0])}${Bi(t[1])}${Bi(t[2])}`},SGR:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Ei(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Ei(e,!0)};${e.x};${e.y}${t}`}},fs=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._coreService=t,this._optionsService=i,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new b),this.onProtocolChange=this._onProtocolChange.event;for(let s of Object.keys(Er))this.addProtocol(s,Er[s]);for(let s of Object.keys(Br))this.addEncoding(s,Br[s]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,i){if(e.deltaY===0||e.shiftKey||t===void 0||i===void 0)return 0;let s=t/i,r=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(r/=s+0,Math.abs(e.deltaY)<50&&(r*=.3),this._wheelPartialScroll+=r,r=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows||e.button===4&&e.action===32||e.button===3&&e.action!==32||e.button!==4&&(e.action===2||e.action===3)||(e.col++,e.row++,e.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._activeEncoding==="SGR_PIXELS"))||!this._protocols[this._activeProtocol].restrict(e))return!1;let t=this._encodings[this._activeEncoding](e);return t&&(this._activeEncoding==="DEFAULT"?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(e&1),up:!!(e&2),drag:!!(e&4),move:!!(e&8),wheel:!!(e&16)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x||e.y!==t.y)return!1}else if(e.col!==t.col||e.row!==t.row)return!1;return!(e.button!==t.button||e.action!==t.action||e.ctrl!==t.ctrl||e.alt!==t.alt||e.shift!==t.shift)}};fs=K([w(0,ce),w(1,je),w(2,he)],fs);var Di=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],sl=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],j;function rl(e,t){let i=0,s=t.length-1,r;if(et[s][1])return!1;for(;s>=i;)if(r=i+s>>1,e>t[r][1])i=r+1;else if(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),s=i===0&&t!==0;if(s){let r=Qe.extractWidth(t);r===0?s=!1:r>i&&(i=r)}return Qe.createPropertyValue(0,i,s)}},Qe=class Jt{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new b,this.onChange=this._onChange.event;let t=new nl;this.register(t),this._active=t.version,this._activeProvider=t}static extractShouldJoin(t){return(t&1)!==0}static extractWidth(t){return t>>1&3}static extractCharKind(t){return t>>3}static createPropertyValue(t,i,s=!1){return(t&16777215)<<3|(i&3)<<1|(s?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(t){if(!this._providers[t])throw new Error(`unknown Unicode version "${t}"`);this._active=t,this._activeProvider=this._providers[t],this._onChange.fire(t)}register(t){this._providers[t.version]=t}wcwidth(t){return this._activeProvider.wcwidth(t)}getStringCellWidth(t){let i=0,s=0,r=t.length;for(let n=0;n=r)return i+this.wcwidth(o);let a=t.charCodeAt(n);56320<=a&&a<=57343?o=(o-55296)*1024+a-56320+65536:i+=this.wcwidth(a)}let h=this.charProperties(o,s),l=Jt.extractWidth(h);Jt.extractShouldJoin(h)&&(l-=Jt.extractWidth(s)),i+=l,s=h}return i}charProperties(t,i){return this._activeProvider.charProperties(t,i)}},ol=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}};function Dr(e){let t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1)?.get(e.cols-1),i=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);i&&t&&(i.isWrapped=t[3]!==0&&t[3]!==32)}var vt=2147483647,al=256,zn=class ps{constructor(t=32,i=32){if(this.maxLength=t,this.maxSubParamsLength=i,i>al)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(t),this.length=0,this._subParams=new Int32Array(i),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(t),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let i=new ps;if(!t.length)return i;for(let s=Array.isArray(t[0])?1:0;s>8,r=this._subParamsIdx[i]&255;r-s>0&&t.push(Array.prototype.slice.call(this._subParams,s,r))}return t}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(t){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=t>vt?vt:t}addSubParam(t){if(this._digitIsSub=!0,!!this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=t>vt?vt:t,this._subParamsIdx[this.length-1]++}}hasSubParams(t){return(this._subParamsIdx[t]&255)-(this._subParamsIdx[t]>>8)>0}getSubParams(t){let i=this._subParamsIdx[t]>>8,s=this._subParamsIdx[t]&255;return s-i>0?this._subParams.subarray(i,s):null}getSubParamsAll(){let t={};for(let i=0;i>8,r=this._subParamsIdx[i]&255;r-s>0&&(t[i]=this._subParams.slice(s,r))}return t}addDigit(t){let i;if(this._rejectDigits||!(i=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let s=this._digitIsSub?this._subParams:this.params,r=s[i-1];s[i-1]=~r?Math.min(r*10+t,vt):t}},mt=[],hl=class{constructor(){this._state=0,this._active=mt,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=mt}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=mt,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||mt,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",at(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){if(this._state!==3){if(this._state===1)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=mt,this._id=-1,this._state=0}}},Kn=class $n{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=at(t,i,s),this._data.length>$n._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};Kn._payloadLimit=1e7;var ge=Kn,St=[],ll=class{constructor(){this._handlers=Object.create(null),this._active=St,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=St}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=St,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||St,!this._active.length)this._handlerFb(this._ident,"HOOK",t);else for(let i=this._active.length-1;i>=0;i--)this._active[i].hook(t)}put(e,t,i){if(!this._active.length)this._handlerFb(this._ident,"PUT",at(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}unhook(e,t=!0){if(!this._active.length)this._handlerFb(this._ident,"UNHOOK",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].unhook(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=St,this._ident=0}},xt=new zn;xt.addParam(0);var Un=class qn{constructor(t){this._handler=t,this._data="",this._params=xt,this._hitLimit=!1}hook(t){this._params=t.length>1||t.params[0]?t.clone():xt,this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=at(t,i,s),this._data.length>qn._payloadLimit&&(this._data="",this._hitLimit=!0))}unhook(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data,this._params),i instanceof Promise))return i.then(s=>(this._params=xt,this._data="",this._hitLimit=!1,s));return this._params=xt,this._data="",this._hitLimit=!1,i}};Un._payloadLimit=1e7;var Rr=Un,rt=[],cl=class{constructor(){this._state=0,this._active=rt,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=rt}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=rt,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||rt,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",at(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){this._state!==3&&(this._state===1&&t0&&this._put(e,t,i))}end(e,t=!0){if(this._state!==0){if(this._state!==3){if(this._state===1){this._active=rt,this._id=-1,this._state=0;return}if(!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}}this._active=rt,this._id=-1,this._state=0}}},Vn=class Yn{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=at(t,i,s),this._data.length>Yn._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};Vn._payloadLimit=1e7;var dl=Vn,ul=class{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rh),i=(o,h)=>t.slice(o,h),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));let n=i(0,15);e.setDefault(1,0),e.addMany(s,0,2,0);for(let o of n)e.addMany([24,26,153,154],o,3,0),e.addMany(i(128,144),o,3,0),e.addMany(i(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158],o,0,7),e.add(159,o,15,14),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,15,14),e.addMany(s,14,16,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany([27,156,24,26],14,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(28,32),9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(i(28,32),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(28,32),10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(28,32),12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(ue,0,2,0),e.add(ue,8,5,8),e.add(ue,6,0,6),e.add(ue,11,0,11),e.add(ue,13,13,13),e.add(ue,14,16,14),e})(),fl=class extends I{constructor(e=_l){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new zn,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(t,i,s)=>{},this._executeHandlerFb=t=>{},this._csiHandlerFb=(t,i)=>{},this._escHandlerFb=t=>{},this._errorHandlerFb=t=>t,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(q(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new hl),this._dcsParser=this._register(new ll),this._apcParser=this._register(new cl),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let r=0;rn||n>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=n}}if(e.final.length!==1)throw new Error("final must be a single byte");let s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){var r;let i=this._identifier(e,[48,126]);(r=this._escHandlers)[i]??(r[i]=[]);let s=this._escHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){var r;let i=this._identifier(e);(r=this._csiHandlers)[i]??(r[i]=[]);let s=this._csiHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return this._apcParser.registerHandler(e,t)}clearApcHandler(e){this._apcParser.clearHandler(e)}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s=0,r=0,n=0,o;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(i===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");let h=this._parseStack.handlers,l=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](this._params),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 4:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&255}for(let h=n;h>8){case 2:for(let u=h+1;;++u){if(u>=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=0&&(o=l[a](this._params),o!==!0);a--)if(o instanceof Promise)return this._preserveStack(3,l,a,r,h),o;a<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}while(++h47&&s<60);h--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:let d=this._escHandlers[this._collect<<8|s],c=d?d.length-1:-1;for(;c>=0&&(o=d[c](),o!==!0);c--)if(o instanceof Promise)return this._preserveStack(4,d,c,r,h),o;c<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let u=h+1;;++u)if(u>=t||(s=e[u])===24||s===26||s===27||s>127&&s=t||(s=e[u])<32||s>127&&s=t||(s=e[u])===24||s===26||s===27||s===156||s>127&&s>4:n>>8}return s}}function Ri(e,t){let i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}function vl(e,t=16){let[i,s,r]=e;return`rgb:${Ri(i,t)}/${Ri(s,t)}/${Ri(r,t)}`}var ml="6.1.0-beta.141",Sl={"(":0,")":1,"*":2,"+":3,"-":1,".":2},qe=131072,Lr=10;function Pr(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var Tr=5e3,Ar=0,wl=class extends I{constructor(e,t,i,s,r,n,o,h,l=new fl){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=n,this._coreMouseService=o,this._unicodeService=h,this._parser=l,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new no,this._utf8Decoder=new oo,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone(),this._onRequestBell=this._register(new b),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new b),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new b),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new b),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new b),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new b),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new b),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new b),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new b),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new b),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new gs(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(a=>this._activeBuffer=a.activeBuffer)),this._parser.setCsiHandlerFallback((a,d)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(a),params:d.toArray()})}),this._parser.setEscHandlerFallback(a=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(a)})}),this._parser.setExecuteHandlerFallback(a=>{this._logService.debug("Unknown EXECUTE code: ",{code:a})}),this._parser.setOscHandlerFallback((a,d,c)=>{this._logService.debug("Unknown OSC code: ",{identifier:a,action:d,data:c})}),this._parser.setDcsHandlerFallback((a,d,c)=>{d==="HOOK"&&(c=c.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(a),action:d,payload:c})}),this._parser.setPrintHandler((a,d,c)=>this.print(a,d,c)),this._parser.registerCsiHandler({final:"@"},a=>this.insertChars(a)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},a=>this.scrollLeft(a)),this._parser.registerCsiHandler({final:"A"},a=>this.cursorUp(a)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},a=>this.scrollRight(a)),this._parser.registerCsiHandler({final:"B"},a=>this.cursorDown(a)),this._parser.registerCsiHandler({final:"C"},a=>this.cursorForward(a)),this._parser.registerCsiHandler({final:"D"},a=>this.cursorBackward(a)),this._parser.registerCsiHandler({final:"E"},a=>this.cursorNextLine(a)),this._parser.registerCsiHandler({final:"F"},a=>this.cursorPrecedingLine(a)),this._parser.registerCsiHandler({final:"G"},a=>this.cursorCharAbsolute(a)),this._parser.registerCsiHandler({final:"H"},a=>this.cursorPosition(a)),this._parser.registerCsiHandler({final:"I"},a=>this.cursorForwardTab(a)),this._parser.registerCsiHandler({final:"J"},a=>this.eraseInDisplay(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},a=>this.eraseInDisplay(a,!0)),this._parser.registerCsiHandler({final:"K"},a=>this.eraseInLine(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},a=>this.eraseInLine(a,!0)),this._parser.registerCsiHandler({final:"L"},a=>this.insertLines(a)),this._parser.registerCsiHandler({final:"M"},a=>this.deleteLines(a)),this._parser.registerCsiHandler({final:"P"},a=>this.deleteChars(a)),this._parser.registerCsiHandler({final:"S"},a=>this.scrollUp(a)),this._parser.registerCsiHandler({final:"T"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"X"},a=>this.eraseChars(a)),this._parser.registerCsiHandler({final:"Z"},a=>this.cursorBackwardTab(a)),this._parser.registerCsiHandler({final:"^"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"`"},a=>this.charPosAbsolute(a)),this._parser.registerCsiHandler({final:"a"},a=>this.hPositionRelative(a)),this._parser.registerCsiHandler({final:"b"},a=>this.repeatPrecedingCharacter(a)),this._parser.registerCsiHandler({final:"c"},a=>this.sendDeviceAttributesPrimary(a)),this._parser.registerCsiHandler({prefix:">",final:"c"},a=>this.sendDeviceAttributesSecondary(a)),this._parser.registerCsiHandler({final:"d"},a=>this.linePosAbsolute(a)),this._parser.registerCsiHandler({final:"e"},a=>this.vPositionRelative(a)),this._parser.registerCsiHandler({final:"f"},a=>this.hVPosition(a)),this._parser.registerCsiHandler({final:"g"},a=>this.tabClear(a)),this._parser.registerCsiHandler({final:"h"},a=>this.setMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"h"},a=>this.setModePrivate(a)),this._parser.registerCsiHandler({final:"l"},a=>this.resetMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"l"},a=>this.resetModePrivate(a)),this._parser.registerCsiHandler({final:"m"},a=>this.charAttributes(a)),this._parser.registerCsiHandler({final:"n"},a=>this.deviceStatus(a)),this._parser.registerCsiHandler({prefix:"?",final:"n"},a=>this.deviceStatusPrivate(a)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},a=>this.softReset(a)),this._parser.registerCsiHandler({prefix:">",final:"q"},a=>this.sendXtVersion(a)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},a=>this.setCursorStyle(a)),this._parser.registerCsiHandler({final:"r"},a=>this.setScrollRegion(a)),this._parser.registerCsiHandler({final:"s"},a=>this.saveCursor(a)),this._parser.registerCsiHandler({final:"t"},a=>this.windowOptions(a)),this._parser.registerCsiHandler({final:"u"},a=>this.restoreCursor(a)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},a=>this.insertColumns(a)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},a=>this.deleteColumns(a)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},a=>this.selectProtected(a)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},a=>this.requestMode(a,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},a=>this.requestMode(a,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},a=>this.kittyKeyboardSet(a)),this._parser.registerCsiHandler({prefix:"?",final:"u"},a=>this.kittyKeyboardQuery(a)),this._parser.registerCsiHandler({prefix:">",final:"u"},a=>this.kittyKeyboardPush(a)),this._parser.registerCsiHandler({prefix:"<",final:"u"},a=>this.kittyKeyboardPop(a)),this._parser.setExecuteHandler("\x07",()=>this.bell()),this._parser.setExecuteHandler(` -`,()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler(" ",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("\x84",()=>this.index()),this._parser.setExecuteHandler("\x85",()=>this.nextLine()),this._parser.setExecuteHandler("\x88",()=>this.tabSet()),this._parser.registerOscHandler(0,new ge(a=>(this.setTitle(a),this.setIconName(a),!0))),this._parser.registerOscHandler(1,new ge(a=>this.setIconName(a))),this._parser.registerOscHandler(2,new ge(a=>this.setTitle(a))),this._parser.registerOscHandler(4,new ge(a=>this.setOrReportIndexedColor(a))),this._parser.registerOscHandler(8,new ge(a=>this.setHyperlink(a))),this._parser.registerOscHandler(10,new ge(a=>this.setOrReportFgColor(a))),this._parser.registerOscHandler(11,new ge(a=>this.setOrReportBgColor(a))),this._parser.registerOscHandler(12,new ge(a=>this.setOrReportCursorColor(a))),this._parser.registerOscHandler(104,new ge(a=>this.restoreIndexedColor(a))),this._parser.registerOscHandler(110,new ge(a=>this.restoreFgColor(a))),this._parser.registerOscHandler(111,new ge(a=>this.restoreBgColor(a))),this._parser.registerOscHandler(112,new ge(a=>this.restoreCursorColor(a))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(let a in X)this._parser.registerEscHandler({intermediates:"(",final:a},()=>this.selectCharset("("+a)),this._parser.registerEscHandler({intermediates:")",final:a},()=>this.selectCharset(")"+a)),this._parser.registerEscHandler({intermediates:"*",final:a},()=>this.selectCharset("*"+a)),this._parser.registerEscHandler({intermediates:"+",final:a},()=>this.selectCharset("+"+a)),this._parser.registerEscHandler({intermediates:"-",final:a},()=>this.selectCharset("-"+a)),this._parser.registerEscHandler({intermediates:".",final:a},()=>this.selectCharset("."+a)),this._parser.registerEscHandler({intermediates:"/",final:a},()=>this.selectCharset("/"+a));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(a=>(this._logService.error("Parsing error: ",a),a)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new Rr((a,d)=>this.requestStatusString(a,d)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=3){let t,i=new Promise((s,r)=>{t=setTimeout(()=>r("#SLOW_TIMEOUT"),Tr)});Promise.race([e,i]).then(()=>{t!==void 0&&clearTimeout(t)},s=>{if(t!==void 0&&clearTimeout(t),s!=="#SLOW_TIMEOUT")throw s;console.warn(`async parser handler taking longer than ${Tr} ms`)})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,n=0,o=this._parseStack.paused;if(o){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>qe&&(n=this._parseStack.position+qe)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e=="string"?` "${e}"`:` "${Array.prototype.map.call(e,a=>String.fromCharCode(a)).join("")}"`}`),this._logService.logLevel===0&&this._logService.trace("parsing data (codes)",typeof e=="string"?e.split("").map(a=>a.charCodeAt(0)):e),this._parseBuffer.lengthqe)for(let a=n;a0&&c.getWidth(this._activeBuffer.x-1)===2&&c.setCellFromCodepoint(this._activeBuffer.x-1,0,1,d);let u=this._parser.precedingJoinState;for(let f=t;fh){if(l){let E=c,A=this._activeBuffer.x-y;if(this._activeBuffer.x=y,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),c=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!c)return;for(y>0&&c instanceof Ct&&c.copyCellsFrom(E,A,0,y,!1);A=0;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d);continue}if(a&&(c.insertCells(this._activeBuffer.x,r-y,this._activeBuffer.getNullCell(d)),c.getWidth(h-1)===2&&c.setCellFromCodepoint(h-1,0,1,d)),c.setCellFromCodepoint(this._activeBuffer.x++,s,r,d),r>0)for(;--r;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d)}this._parser.precedingJoinState=u,this._activeBuffer.x0&&c.getWidth(this._activeBuffer.x)===0&&!c.hasContent(this._activeBuffer.x)&&c.setCellFromCodepoint(this._activeBuffer.x,0,1,d),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final==="t"&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,i=>Pr(i.params[0],this._optionsService.rawOptions.windowOptions)?t(i):!0):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new Rr(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new ge(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new dl(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){let n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n&&(n.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(n.isWrapped=!1))}_resetBufferLine(e,t=!1){let i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let i;switch(e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);i=this._bufferService.cols){let r=this._activeBuffer.lines.get(i+1);r&&(r.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+i)?.getTrimmedLength(););for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let s=this._activeBuffer.lines.length-this._bufferService.rows;s>0&&(this._activeBuffer.lines.trimStart(s),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-s,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-s,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let l=h;for(let a=1;a0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("\x1B[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("\x1B[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("\x1B[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("\x1B[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("\x1B[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`\x1BP>|xterm.js(${ml})\x1B\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(p[p.NOT_RECOGNIZED=0]="NOT_RECOGNIZED",p[p.SET=1]="SET",p[p.RESET=2]="RESET",p[p.PERMANENTLY_SET=3]="PERMANENTLY_SET",p[p.PERMANENTLY_RESET=4]="PERMANENTLY_RESET"))(void 0||(i={}));let s=this._coreService.decPrivateModes,{activeProtocol:r,activeEncoding:n}=this._coreMouseService,o=this._coreService,{buffers:h,cols:l}=this._bufferService,{active:a,alt:d}=h,c=this._optionsService.rawOptions,u=(p,y)=>(o.triggerDataEvent(`\x1B[${t?"":"?"}${p};${y}$y`),!0),f=p=>p?1:2,_=e.params[0];return t?_===2?u(_,4):_===4?u(_,f(o.modes.insertMode)):_===12?u(_,3):_===20?u(_,f(c.convertEol)):u(_,0):_===1?u(_,f(s.applicationCursorKeys)):_===3?u(_,c.windowOptions.setWinLines?l===80?2:l===132?1:0:0):_===6?u(_,f(s.origin)):_===7?u(_,f(s.wraparound)):_===8?u(_,3):_===9?u(_,f(r==="X10")):_===12?u(_,f(c.cursorBlink)):_===25?u(_,f(!o.isCursorHidden)):_===45?u(_,f(s.reverseWraparound)):_===66?u(_,f(s.applicationKeypad)):_===67?u(_,4):_===1e3?u(_,f(r==="VT200")):_===1002?u(_,f(r==="DRAG")):_===1003?u(_,f(r==="ANY")):_===1004?u(_,f(s.sendFocus)):_===1005?u(_,4):_===1006?u(_,f(n==="SGR")):_===1015?u(_,4):_===1016?u(_,f(n==="SGR_PIXELS")):_===1048?u(_,1):_===47||_===1047||_===1049?u(_,f(a===d)):_===2004?u(_,f(s.bracketedPasteMode)):_===2026?u(_,f(s.synchronizedOutput)):_===9001&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?u(_,f(s.win32InputMode)):u(_,0)}_updateAttrColor(e,t,i,s,r){return t===2?(e|=50331648,e&=-16777216,e|=Pt.fromColorRGB([i,s,r])):t===5&&(e&=-50331904,e|=33554432|i&255),e}_extractColor(e,t,i){let s=[0,0,-1,0,0,0],r=0,n=0;do{if(s[n+r]=e.params[t+n],e.hasSubParams(t+n)){let o=e.getSubParams(t+n),h=0;do s[1]===5&&(r=1),s[n+h+1+r]=o[h];while(++h=2||s[1]===2&&n+r>=5)break;s[1]&&(r=1)}while(++n+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=Q.fg,e.bg=Q.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,i,s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-50331904,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-50331904,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-50331904,s.fg|=16777216|i-90|8):i>=100&&i<=107?(s.bg&=-50331904,s.bg|=16777216|i-100|8):i===0?this._processSGR0(s):i===1?s.fg|=134217728:i===3?s.bg|=67108864:i===4?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):i===5?s.fg|=536870912:i===7?s.fg|=67108864:i===8?s.fg|=1073741824:i===9?s.fg|=2147483648:i===2?s.bg|=134217728:i===21?this._processUnderline(2,s):i===22?(s.fg&=-134217729,s.bg&=-134217729):i===23?s.bg&=-67108865:i===24?(s.fg&=-268435457,this._processUnderline(0,s)):i===25?s.fg&=-536870913:i===27?s.fg&=-67108865:i===28?s.fg&=-1073741825:i===29?s.fg&=2147483647:i===39?(s.fg&=-67108864,s.fg|=Q.fg&16777215):i===49?(s.bg&=-67108864,s.bg|=Q.bg&16777215):i===38||i===48||i===58?r+=this._extractColor(e,r,s):i===53?s.bg|=1073741824:i===55?s.bg&=-1073741825:i===221&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.fg&=-134217729:i===222&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.bg&=-134217729:i===59?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("\x1B[0n");break;case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[${t};${i}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[?${t};${i}R`);break;case 15:break;case 25:break;case 26:break;case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??!0)&&this._onRequestColorSchemeQuery.fire();break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=Q.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar";break}let i=t%2===1;this._coreService.decPrivateModes.cursorBlink=i}return!0}setScrollRegion(e){let t=e.params[0]||1,i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||i===0)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!Pr(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`\x1B[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>Lr&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>Lr&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let t=0;t1;){let s=i.shift(),r=i.shift();if(/^\d+$/.exec(s)){let n=parseInt(s);if(Or(n))if(r==="?")t.push({type:0,index:n});else{let o=Mr(r);o&&t.push({type:1,index:n,color:o})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(";");if(t===-1)return!0;let i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):i.trim()?!1:this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let i=e.split(":"),s,r=i.findIndex(n=>n.startsWith("id="));return r!==-1&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let i=e.split(";");for(let s=0;s=this._specialColors.length);++s,++t)if(i[s]==="?")this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let r=Mr(i[s]);r&&this._onColor.fire([{type:1,index:this._specialColors[t],color:r}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],i=e.split(";");for(let s=0;s=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new ke;e.content=1<<22|69,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`\x1B${o}\x1B\\`),!0),s=this._bufferService.buffer,r=this._optionsService.rawOptions;return i(e==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:e==='"p'?'P1$r61;1"p':e==="r"?`P1$r${s.scrollTop+1};${s.scrollBottom+1}r`:e==="m"?"P1$r0m":e===" q"?`P1$r${{block:2,underline:4,bar:6}[r.cursorStyle]-(r.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t;break}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`\x1B[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let r=0;r0;r++)i.flags=s.pop();return s.length===0&&t>0&&(i.flags=0),!0}},gs=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(Ar=e,e=t,t=Ar),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};gs=K([w(0,ce)],gs);function Or(e){return 0<=e&&e<256}var bl=5e7,Ir=12,yl=50,Cl=class extends I{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._isSyncWriting)return;this._isSyncWriting=!0;let e;for(;e=this._writeBuffer.shift();){this._action(e);let t=this._callbacks.shift();t&&t()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1}writeSync(e,t){if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let i;for(;i=this._writeBuffer.shift();){this._action(i);let s=this._callbacks.shift();s&&s()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>bl)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){let i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let s=this._writeBuffer[this._bufferOffset],r=this._action(s,t);if(r){let o=h=>performance.now()-i>=Ir?setTimeout(()=>this._innerWrite(0,h)):this._innerWrite(i,h);r.catch(h=>(queueMicrotask(()=>{throw h}),Promise.resolve(!1))).then(o);return}let n=this._callbacks[this._bufferOffset];if(n&&n(),this._bufferOffset++,this._pendingData-=s.length,performance.now()-i>=Ir)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>yl&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}},vs=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){let t=this._bufferService.buffer;if(e.id===void 0){let h=t.addMarker(t.ybase+t.y),l={data:e,id:this._nextId++,lines:[h]};return h.onDispose(()=>this._removeMarkerFromLink(l,h)),this._dataByLinkId.set(l.id,l),l.id}let i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;let n=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[n]};return n.onDispose(()=>this._removeMarkerFromLink(o,n)),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){let i=this._dataByLinkId.get(e);if(i&&i.lines.every(s=>s.line!==t)){let s=this._bufferService.buffer.addMarker(t);i.lines.push(s),s.onDispose(()=>this._removeMarkerFromLink(i,s))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){let i=e.lines.indexOf(t);i!==-1&&(e.lines.splice(i,1),e.lines.length===0&&(e.data.id!==void 0&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};vs=K([w(0,ce)],vs);var Nr=!1,kl=class extends I{constructor(e){super(),this._windowsWrappingHeuristics=this._register(new ot),this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onData=this._register(new b),this.onData=this._onData.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new b),this._instantiationService=new qh,this.optionsService=this._register(new tl(e)),this._instantiationService.setService(he,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(us)),this._instantiationService.setService(ce,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(ds)),this._instantiationService.setService(Gr,this._logService),this.coreService=this._register(this._instantiationService.createInstance(_s)),this._instantiationService.setService(je,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(fs)),this._instantiationService.setService(Xr,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(Qe)),this._instantiationService.setService(co,this.unicodeService),this._charsetService=this._instantiationService.createInstance(ol),this._instantiationService.setService(lo,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(vs),this._instantiationService.setService(Jr,this._oscLinkService),this._inputHandler=this._register(new wl(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(_e.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(_e.forward(this._bufferService.onResize,this._onResize)),this._register(_e.forward(this.coreService.onData,this._onData)),this._register(_e.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new Cl((t,i)=>this._inputHandler.parse(t,i))),this._register(_e.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new b),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!Nr&&(this._logService.warn("writeSync is unreliable and will be removed soon."),Nr=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,Hn),t=Math.max(t,Wn),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.buildNumber!==void 0&&t.buildNumber!==void 0&&(e=t.backend==="conpty"&&t.buildNumber<21376),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Dr.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>(Dr(this._bufferService),!1))),this._windowsWrappingHeuristics.value=q(()=>{for(let t of e)t.dispose()})}}},U=0,xl=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new oi,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new oi,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){let e=this._insertedValues.sort((r,n)=>this._getKey(r)-this._getKey(n)),t=0,i=0,s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),this._array.length===0)return!1;let t=this._getKey(e);if(t===void 0||(U=this._search(t),U===-1)||this._getKey(this._array[U])!==t)return!1;do if(this._array[U]===e)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(U),!0;while(++Ur-n),t=0,i=new Array(this._array.length-e.length),s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(U=this._search(e),!(U<0||U>=this._array.length)&&this._getKey(this._array[U])===e))do yield this._array[U];while(++U=this._array.length)&&this._getKey(this._array[U])===e))do t(this._array[U]);while(++U=t;){let s=t+i>>1,r=this._getKey(this._array[s]);if(r>e)i=s-1;else if(r0&&this._getKey(this._array[s-1])===e;)s--;return s}}return t}},Mi=0,Fr=0,Li=0,Hr=0,El=class extends I{constructor(){super(),this._decorations=new xl(e=>e?.marker.line),this._onDecorationRegistered=this._register(new b),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new b),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register(q(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new Bl(e);if(t){let i=t.marker.onDispose(()=>t.dispose()),s=t.onDispose(()=>{s.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),i.dispose())});this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,i){let s=0,r=0,n=0,o=0;for(let h of this._decorations.values())n=h.marker.line,o=n+(h.options.height??1),!(t=o)&&(s=h.options.x??0,r=s+(h.options.width??1),e>=s&&e=Hr)&&(Mi=r.options.x??0,Fr=Mi+(r.options.width??1),e>=Mi&&e=this._debounceThresholdMS)this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){let r=s-this._lastRefreshMs,n=this._debounceThresholdMS-r;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},n)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}},Wr=20,zr=!1,ai=class extends I{constructor(e,t,i,s){super(),this._terminal=e,this._coreBrowserService=i,this._renderService=s,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="";let r=this._coreBrowserService.mainDocument;this._accessibilityContainer=r.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=r.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let n=0;nthis._handleBoundaryFocus(n,0),this._bottomBoundaryFocusListener=n=>this._handleBoundaryFocus(n,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Rl(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");zr?(this._accessibilityContainer.classList.add("debug"),this._rowContainer.classList.add("debug"),this._debugRootContainer=r.createElement("div"),this._debugRootContainer.classList.add("xterm"),this._debugRootContainer.appendChild(r.createTextNode("------start a11y------")),this._debugRootContainer.appendChild(this._accessibilityContainer),this._debugRootContainer.appendChild(r.createTextNode("------end a11y------")),this._terminal.element.insertAdjacentElement("afterend",this._debugRootContainer)):this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(n=>this._handleResize(n.rows))),this._register(this._terminal.onRender(n=>this._refreshRows(n.start,n.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(n=>this._handleChar(n))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` -`))),this._register(this._terminal.onA11yTab(n=>this._handleTab(n))),this._register(this._terminal.onKey(n=>this._handleKey(n.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(B(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(q(()=>{zr?this._debugRootContainer.remove():this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` -`&&(this._liveRegionLineCount++,this._liveRegionLineCount===Wr+1&&(this._liveRegion.textContent=Zt.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){let n=i.lines.get(i.ydisp+r),o=[],h=n?.translateToString(!0,void 0,void 0,o)||"",l=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(h.length===0?(a.textContent="\xA0",this._rowColumns.set(a,[0,1])):(a.textContent=h,this._rowColumns.set(a,o)),a.setAttribute("aria-posinset",l),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent===Zt.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){let i=e.target,s=this._rowElements[t===0?1:this._rowElements.length-2],r=i.getAttribute("aria-posinset"),n=t===0?"1":`${this._terminal.buffer.lines.length}`;if(r===n||e.relatedTarget!==s)return;let o,h;if(t===0?(o=i,h=this._rowElements.pop(),this._rowContainer.removeChild(h)):(o=this._rowElements.shift(),h=i,this._rowContainer.removeChild(o)),o.removeEventListener("focus",this._topBoundaryFocusListener),h.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){let l=this._createAccessibilityTreeNode();this._rowElements.unshift(l),this._rowContainer.insertAdjacentElement("afterbegin",l)}else{let l=this._createAccessibilityTreeNode();this._rowElements.push(l),this._rowContainer.appendChild(l)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error("anchorNode and/or focusNode are null");return}let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;let r=({node:h,offset:l})=>{let a=h instanceof Text?h.parentNode:h,d=parseInt(a?.getAttribute("aria-posinset"),10)-1;if(isNaN(d))return console.warn("row is invalid. Race condition?"),null;let c=this._rowColumns.get(a);if(!c)return console.warn("columns is null. Race condition?"),null;let u=l=this._terminal.cols&&(++d,u=0),{row:d,column:u}},n=r(t),o=r(i);if(!(!n||!o)){if(n.row>o.row||n.row===o.row&&n.column>=o.column)throw new Error("invalid range");this._terminal.select(n.column,n.row,(o.row-n.row)*this._terminal.cols-n.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;te;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Ii(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(B(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(B(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(B(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(B(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;let i=e.composedPath();for(let s=0;s{s?.forEach(r=>{r.link.dispose&&r.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(let[s,r]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(s)&&(i=this._checkLinkProviderResult(s,e,i)):r.provideLinks(e.y,n=>{if(this._isMouseOut)return;let o=n?.map(h=>({link:h}));this._activeProviderReplies?.set(s,o),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let i=new Set;for(let s=0;se?this._bufferService.cols:o.link.range.end.x;for(let a=h;a<=l;a++){if(i.has(a)){r.splice(n--,1);break}i.add(a)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;let s=this._activeProviderReplies.get(e),r=!1;for(let n=0;nthis._linkAtPosition(o.link,t));n&&(i=!0,this._handleNewLink(n))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let n=0;nthis._linkAtPosition(h.link,t));if(o){i=!0,this._handleNewLink(o);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink&&Ml(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Ii(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0?!0:e.link.decorations.underline,pointerCursor:e.link.decorations===void 0?!0:e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:i=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==i&&(this._currentLink.state.decorations.pointerCursor=i,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",i))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:i=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==i&&(this._currentLink.state.decorations.underline=i,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,i))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(i=>{if(!this._currentLink)return;let s=i.start===0?0:i.start+1+this._bufferService.buffer.ydisp,r=this._bufferService.buffer.ydisp+1+i.end;if(this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=r&&(this._clearCurrentLink(s,r),this._lastMouseEvent)){let n=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);n&&this._askForLink(n,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){let i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){let i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t,i){let s=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(s)return{x:s[0],y:s[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};ms=K([w(1,ws),w(2,Ne),w(3,ce),w(4,Qr)],ms);function Ml(e,t){return e.text===t.text&&e.range.start.x===t.range.start.x&&e.range.start.y===t.range.start.y&&e.range.end.x===t.range.end.x&&e.range.end.y===t.range.end.y}var Ll=class extends kl{constructor(e={}){super(e),this._linkifier=this._register(new ot),this.browser=Dn,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new ot),this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new b),this.onKey=this._onKey.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new b),this.onBell=this._onBell.event,this._onFocus=this._register(new b),this._onBlur=this._register(new b),this._onA11yCharEmitter=this._register(new b),this._onA11yTabEmitter=this._register(new b),this._onWillOpen=this._register(new b),this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._setup(),this._decorationService=this._instantiationService.createInstance(El),this._instantiationService.setService(Tt,this._decorationService),this._keyboardService=this._instantiationService.createInstance(cs),this._instantiationService.setService(fo,this._keyboardService),this._linkProviderService=this._instantiationService.createInstance(mh),this._instantiationService.setService(Qr,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(Oi)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(t=>this.refresh(t?.start??0,t?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(t=>this._reportWindowsOptions(t))),this._register(this._inputHandler.onColor(t=>this._handleColorEvent(t))),this._register(_e.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(_e.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(_e.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(_e.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(t=>this._afterResize(t.cols,t.rows))),this._register(q(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}get dimensions(){if(!this._renderService)return;let e=this._renderService.dimensions;return{css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}}_handleColorEvent(e){if(this._themeService)for(let t of e){let i,s="";switch(t.index){case 256:i="foreground",s="10";break;case 257:i="background",s="11";break;case 258:i="cursor",s="12";break;default:i="ansi",s="4;"+t.index}switch(t.type){case 0:let r=N.toColorRGB(i==="ansi"?this._themeService.colors.ansi[t.index]:this._themeService.colors[i]);this.coreService.triggerDataEvent(`\x1B]${s};${vl(r)}\x1B\\`);break;case 1:if(i==="ansi")this._themeService.modifyColors(n=>n.ansi[t.index]=V.toColor(...t.color));else{let n=i;this._themeService.modifyColors(o=>o[n]=V.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_reportColorScheme(){if(!this._themeService)return;let e=oe.relativeLuminance(this._themeService.colors.background.rgba>>8),t=oe.relativeLuminance(this._themeService.colors.foreground.rgba>>8),i=e{this.hasSelection()&&so(t,this._selectionService)}));let e=t=>ro(t,this.textarea,this.coreService,this.optionsService);this._register(B(this.textarea,"paste",e)),this._register(B(this.element,"paste",e)),Rn?this._register(B(this.element,"mousedown",t=>{t.button===2&&Fs(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(B(this.element,"contextmenu",t=>{Fs(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),Ps&&this._register(B(this.element,"auxclick",t=>{t.button===1&&qr(t,this.textarea,this.screenElement)}))}_bindKeys(){this._register(B(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(B(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(B(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(B(this.textarea,"compositionstart",()=>this._compositionHelper.compositionstart())),this._register(B(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(B(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(B(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",r=>this.element.classList.toggle("allow-transparency",r))),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(B(this.screenElement,"mousemove",r=>this.updateCursorStyle(r))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",Ti.get()),Pn||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(gh,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<"u"?window.document:null)),this._instantiationService.setService(Ie,this._coreBrowserService),this._register(B(this.textarea,"focus",r=>this._handleTextAreaFocus(r))),this._register(B(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(ns,this._document,this._helperContainer),this._instantiationService.setService(hi,this._charSizeService),this._themeService=this._instantiationService.createInstance(ls),this._instantiationService.setService(ht,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(ni),this._instantiationService.setService(Zr,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(as,this.rows,this.screenElement)),this._instantiationService.setService(Ne,this._renderService),this._register(this._renderService.onRenderedViewportChange(r=>this._onRender.fire(r))),this._register(this._renderService.onDimensionsChange(r=>this._onDimensionsChange.fire({css:{canvas:{...r.css.canvas},cell:{...r.css.cell}},device:{canvas:{...r.device.canvas},cell:{...r.device.cell},char:{...r.device.char}}}))),this.onResize(r=>this._renderService.resize(r.cols,r.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(is,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(os),this._instantiationService.setService(ws,this._mouseService);let s=this._linkifier.value=this._register(this._instantiationService.createInstance(ms,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(es,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(r=>{super.scrollLines(r,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(hs,this.element,this.screenElement,s)),this._instantiationService.setService(_o,this._selectionService),this._register(this._selectionService.onRequestScrollLines(r=>this.scrollLines(r.amount,r.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(r=>this._renderService.handleSelectionChanged(r.start,r.end,r.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(r=>{this.textarea.value=r,this.textarea.focus(),this.textarea.select()})),this._register(_e.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(ts,this.screenElement)),this._register(B(this.element,"mousedown",r=>this._selectionService.handleMouseDown(r))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(ai,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",r=>this._handleScreenReaderModeOptionChange(r))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ri,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRuler",r=>{!this._overviewRulerRenderer&&r&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(ri,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(rs,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,t=this.element;function i(n){let o=e._mouseService?.getMouseReportCoords(n,e.screenElement);if(!o)return!1;let h,l;switch(n.overrideType||n.type){case"mousemove":l=32,n.buttons===void 0?(h=3,n.button!==void 0&&(h=n.button<3?n.button:3)):h=n.buttons&1?0:n.buttons&4?1:n.buttons&2?2:3;break;case"mouseup":l=0,h=n.button<3?n.button:3;break;case"mousedown":l=1,h=n.button<3?n.button:3;break;case"wheel":if(e._customWheelEventHandler&&e._customWheelEventHandler(n)===!1)return!1;let a=n.deltaY;if(a===0||e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;l=a<0?0:1,h=4;break;default:return!1}return l===void 0||h===void 0||h>4?!1:e.coreMouseService.triggerMouseEvent({col:o.col,row:o.row,x:o.x,y:o.y,button:h,action:l,ctrl:n.ctrlKey,alt:n.altKey,shift:n.shiftKey})}let s={mouseup:null,wheel:null,mousedrag:null,mousemove:null},r={mouseup:n=>{i(n),n.buttons||(this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag))},wheel:n=>(i(n),n.preventDefault(),n.stopPropagation(),!1),mousedrag:n=>{n.buttons&&i(n)},mousemove:n=>{n.buttons||i(n)}};this._register(this.coreMouseService.onProtocolChange(n=>{n?(this.optionsService.rawOptions.logLevel==="debug"&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(n)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),n&8?s.mousemove||(t.addEventListener("mousemove",r.mousemove),s.mousemove=r.mousemove):(t.removeEventListener("mousemove",s.mousemove),s.mousemove=null),n&16?s.wheel||(t.addEventListener("wheel",r.wheel,{passive:!1}),s.wheel=r.wheel):(t.removeEventListener("wheel",s.wheel),s.wheel=null),n&2?s.mouseup??(s.mouseup=r.mouseup):(this._document.removeEventListener("mouseup",s.mouseup),s.mouseup=null),n&4?s.mousedrag??(s.mousedrag=r.mousedrag):(this._document.removeEventListener("mousemove",s.mousedrag),s.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(q(()=>{s.mouseup&&this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag)})),this._register(B(t,"mousedown",n=>{n.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(n))&&(i(n),s.mouseup&&this._document.addEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.addEventListener("mousemove",s.mousedrag))})),this._register(B(t,"wheel",n=>{if(!s.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(n)===!1)return!1;if(!this.buffer.hasScrollback){if(n.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return n.preventDefault(),n.stopPropagation(),!1;let o="\x1B"+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(n.deltaY<0?"A":"B");return this.coreService.triggerDataEvent(o,!0),n.preventDefault(),n.stopPropagation(),!1}}},{passive:!1}))}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){Ur(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),i.type===3||i.type===2){let r=this.rows-1;return this.scrollLines(i.type===2?-r:r),e.preventDefault(),e.stopPropagation(),!1}if(i.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)||!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;(i.key===""||i.key==="\r")&&(this.textarea.value="");let s=this._keyboardService.useWin32InputMode&&Pi(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){let i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return t.type==="keypress"?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return;Pi(e)||this.focus();let t=this._keyboardService.evaluateKeyUp(e);if(t?.key){let i=this._keyboardService.useWin32InputMode&&Pi(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType==="insertText"&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(!(this.buffer.ybase===0&&this.buffer.y===0)){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){let i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new ke)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}},Kr=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){let t=this._buffer.lines.get(e);if(t)return new Tl(t)}getNullCell(){return new ke}},Al=class extends I{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new b),this.onBufferChange=this._onBufferChange.event,this._normal=new Kr(this._core.buffers.normal,"normal"),this._alternate=new Kr(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}},Ol=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,i=>t(i.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(i,s)=>t(i,s.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}},Il=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}},Nl=["cols","rows"],De=0,jn=class extends I{constructor(e){super(),this._core=this._register(new Ll(e)),this._addonManager=this._register(new Pl),this._publicOptions={...this._core.options};let t=s=>this._core.options[s],i=(s,r)=>{this._checkReadonlyOptions(s),this._core.options[s]=r};for(let s in this._core.options){let r={get:t.bind(this,s),set:i.bind(this,s)};Object.defineProperty(this._publicOptions,s,r)}}_checkReadonlyOptions(e){if(Nl.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get parser(){return this._parser??(this._parser=new Ol(this._core))}get unicode(){return this._checkProposedApi(),new Il(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??(this._buffer=this._register(new Al(this._core)))}get markers(){return this._core.markers}get modes(){let e=this._core.coreService.decPrivateModes,t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any";break}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(let t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write(`\r -`,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return Ti.get()},set promptLabel(e){Ti.set(e)},get tooMuchOutput(){return Zt.get()},set tooMuchOutput(e){Zt.set(e)}}}_verifyIntegers(...e){for(De of e)if(De===1/0||isNaN(De)||De%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(De of e)if(De&&(De===1/0||isNaN(De)||De%1!==0||De<0))throw new Error("This API only accepts positive integers")}};/** +`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),As&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s||!t?!1:this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){let i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!i||!s?!1:this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=pr(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let s=this._getMouseBufferCoords(e);return s?(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=Ps(this._coreBrowserService.window,e,this._screenElement)[1],i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-Ei),Ei),t/=Ei,t/Math.abs(t)+Math.round(t*(Fh-1)))}shouldForceSelection(e){return Rt?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),Hh)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){let t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);let i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&i.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(Rt&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&tthis._handleTrim(t))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){let r=e.loadCell(s,this._workCell).getChars().length;this._workCell.getWidth()===0?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;let r=this._bufferService.buffer,n=r.lines.get(e[1]);if(!n)return;let o=r.translateBufferLineToString(e[1],!1),h=this._convertViewportColToCharacterIndex(n,e[0]),l=h,a=e[0]-h,d=0,c=0,u=0,f=0;if(o.charAt(h)===" "){for(;h>0&&o.charAt(h-1)===" ";)h--;for(;l1&&(f+=A-1,l+=A-1);y>0&&h>0&&!this._isCharWordSeparator(n.loadCell(y-1,this._workCell));){n.loadCell(y-1,this._workCell);let R=this._workCell.getChars().length;this._workCell.getWidth()===0?(d++,y--):R>1&&(u+=R-1,h-=R-1),h--,y--}for(;E1&&(f+=R-1,l+=R-1),l++,E++}}l++;let _=h+a-d+u,p=Math.min(this._bufferService.cols,l-h+d+c-u-f);if(!(!t&&o.slice(h,l).trim()==="")){if(i&&_===0&&n.getCodePoint(0)!==32){let y=r.lines.get(e[1]-1);if(y&&n.isWrapped&&y.getCodePoint(this._bufferService.cols-1)!==32){let E=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(E){let A=this._bufferService.cols-E.start;_-=A,p+=A}}}if(s&&_+p===this._bufferService.cols&&n.getCodePoint(this._bufferService.cols-1)!==32){let y=r.lines.get(e[1]+1);if(y?.isWrapped&&y.getCodePoint(0)!==32){let E=this._getWordAt([0,e[1]+1],!1,!1,!0);E&&(p+=E.length)}}return{start:_,length:p}}}_selectWordAt(e,t){let i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return e.getWidth()===0?!1:this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=pr(i,this._bufferService.cols)}};cs=K([w(3,ce),w(4,Xe),w(5,ys),w(6,he),w(7,Fe),w(8,Ne)],cs);var gr=class{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}},vr=class{constructor(){this._color=new gr,this._css=new gr}setCss(e,t,i){this._css.set(e,t,i)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,i){this._color.set(e,t,i)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}},Y=Object.freeze((()=>{let e=[z.toColor("#2e3436"),z.toColor("#cc0000"),z.toColor("#4e9a06"),z.toColor("#c4a000"),z.toColor("#3465a4"),z.toColor("#75507b"),z.toColor("#06989a"),z.toColor("#d3d7cf"),z.toColor("#555753"),z.toColor("#ef2929"),z.toColor("#8ae234"),z.toColor("#fce94f"),z.toColor("#729fcf"),z.toColor("#ad7fa8"),z.toColor("#34e2e2"),z.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){let s=t[i/36%6|0],r=t[i/6%6|0],n=t[i%6];e.push({css:V.toCss(s,r,n),rgba:V.toRgba(s,r,n)})}for(let i=0;i<24;i++){let s=8+i*10;e.push({css:V.toCss(s,s,s),rgba:V.toRgba(s,s,s)})}return e})()),Ze=z.toColor("#ffffff"),Ct=z.toColor("#000000"),mr=z.toColor("#ffffff"),Sr=Ct,vt={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},$h=Ze,ds=class extends I{constructor(e){super(),this._optionsService=e,this._contrastCache=new vr,this._halfContrastCache=new vr,this._onChangeColors=this._register(new b),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:Ze,background:Ct,cursor:mr,cursorAccent:Sr,selectionForeground:void 0,selectionBackgroundTransparent:vt,selectionBackgroundOpaque:N.blend(Ct,vt),selectionInactiveBackgroundTransparent:vt,selectionInactiveBackgroundOpaque:N.blend(Ct,vt),scrollbarSliderBackground:N.opacity(Ze,.2),scrollbarSliderHoverBackground:N.opacity(Ze,.4),scrollbarSliderActiveBackground:N.opacity(Ze,.5),overviewRulerBorder:Ze,ansi:Y.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=O(e.foreground,Ze),t.background=O(e.background,Ct),t.cursor=N.blend(t.background,O(e.cursor,mr)),t.cursorAccent=N.blend(t.background,O(e.cursorAccent,Sr)),t.selectionBackgroundTransparent=O(e.selectionBackground,vt),t.selectionBackgroundOpaque=N.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=O(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=N.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?O(e.selectionForeground,ur):void 0,t.selectionForeground===ur&&(t.selectionForeground=void 0),N.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=N.opacity(t.selectionBackgroundTransparent,.3)),N.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=N.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=O(e.scrollbarSliderBackground,N.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=O(e.scrollbarSliderHoverBackground,N.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=O(e.scrollbarSliderActiveBackground,N.opacity(t.foreground,.5)),t.overviewRulerBorder=O(e.overviewRulerBorder,$h),t.ansi=Y.slice(),t.ansi[0]=O(e.black,Y[0]),t.ansi[1]=O(e.red,Y[1]),t.ansi[2]=O(e.green,Y[2]),t.ansi[3]=O(e.yellow,Y[3]),t.ansi[4]=O(e.blue,Y[4]),t.ansi[5]=O(e.magenta,Y[5]),t.ansi[6]=O(e.cyan,Y[6]),t.ansi[7]=O(e.white,Y[7]),t.ansi[8]=O(e.brightBlack,Y[8]),t.ansi[9]=O(e.brightRed,Y[9]),t.ansi[10]=O(e.brightGreen,Y[10]),t.ansi[11]=O(e.brightYellow,Y[11]),t.ansi[12]=O(e.brightBlue,Y[12]),t.ansi[13]=O(e.brightMagenta,Y[13]),t.ansi[14]=O(e.brightCyan,Y[14]),t.ansi[15]=O(e.brightWhite,Y[15]),e.extendedAnsi){let i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;s"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};function qh(e,t,i,s){let r={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:e.key==="UIKeyInputUpArrow"?t?r.key="\x1BOA":r.key="\x1B[A":e.key==="UIKeyInputLeftArrow"?t?r.key="\x1BOD":r.key="\x1B[D":e.key==="UIKeyInputRightArrow"?t?r.key="\x1BOC":r.key="\x1B[C":e.key==="UIKeyInputDownArrow"&&(t?r.key="\x1BOB":r.key="\x1B[B");break;case 8:r.key=e.ctrlKey?"\b":"\x7F",e.altKey&&(r.key="\x1B"+r.key);break;case 9:if(e.shiftKey){r.key="\x1B[Z";break}r.key=" ",r.cancel=!0;break;case 13:r.key=e.altKey?"\x1B\r":"\r",r.cancel=!0;break;case 27:r.key="\x1B",e.altKey&&(r.key="\x1B\x1B"),r.cancel=!0;break;case 37:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"D":t?r.key="\x1BOD":r.key="\x1B[D";break;case 39:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"C":t?r.key="\x1BOC":r.key="\x1B[C";break;case 38:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"A":t?r.key="\x1BOA":r.key="\x1B[A";break;case 40:if(e.metaKey)break;n?r.key="\x1B[1;"+(n+1)+"B":t?r.key="\x1BOB":r.key="\x1B[B";break;case 45:!e.shiftKey&&!e.ctrlKey&&(r.key="\x1B[2~");break;case 46:n?r.key="\x1B[3;"+(n+1)+"~":r.key="\x1B[3~";break;case 36:n?r.key="\x1B[1;"+(n+1)+"H":t?r.key="\x1BOH":r.key="\x1B[H";break;case 35:n?r.key="\x1B[1;"+(n+1)+"F":t?r.key="\x1BOF":r.key="\x1B[F";break;case 33:e.shiftKey?r.type=2:e.ctrlKey?r.key="\x1B[5;"+(n+1)+"~":r.key="\x1B[5~";break;case 34:e.shiftKey?r.type=3:e.ctrlKey?r.key="\x1B[6;"+(n+1)+"~":r.key="\x1B[6~";break;case 112:n?r.key="\x1B[1;"+(n+1)+"P":r.key="\x1BOP";break;case 113:n?r.key="\x1B[1;"+(n+1)+"Q":r.key="\x1BOQ";break;case 114:n?r.key="\x1B[1;"+(n+1)+"R":r.key="\x1BOR";break;case 115:n?r.key="\x1B[1;"+(n+1)+"S":r.key="\x1BOS";break;case 116:n?r.key="\x1B[15;"+(n+1)+"~":r.key="\x1B[15~";break;case 117:n?r.key="\x1B[17;"+(n+1)+"~":r.key="\x1B[17~";break;case 118:n?r.key="\x1B[18;"+(n+1)+"~":r.key="\x1B[18~";break;case 119:n?r.key="\x1B[19;"+(n+1)+"~":r.key="\x1B[19~";break;case 120:n?r.key="\x1B[20;"+(n+1)+"~":r.key="\x1B[20~";break;case 121:n?r.key="\x1B[21;"+(n+1)+"~":r.key="\x1B[21~";break;case 122:n?r.key="\x1B[23;"+(n+1)+"~":r.key="\x1B[23~";break;case 123:n?r.key="\x1B[24;"+(n+1)+"~":r.key="\x1B[24~";break;default:if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&!e.metaKey)e.keyCode>=65&&e.keyCode<=90?r.key=String.fromCharCode(e.keyCode-64):e.keyCode===32?r.key="\0":e.keyCode>=51&&e.keyCode<=55?r.key=String.fromCharCode(e.keyCode-51+27):e.keyCode===56?r.key="\x7F":e.key==="/"?r.key="":e.keyCode===219?r.key="\x1B":e.keyCode===220?r.key="":e.keyCode===221&&(r.key="");else if((!i||s)&&e.altKey&&!e.metaKey){let o=Uh[e.keyCode]?.[e.shiftKey?1:0];if(o)r.key="\x1B"+o;else if(e.keyCode>=65&&e.keyCode<=90){let h=e.ctrlKey?e.keyCode-64:e.keyCode+32,l=String.fromCharCode(h);e.shiftKey&&(l=l.toUpperCase()),r.key="\x1B"+l}else if(e.keyCode===32)r.key="\x1B"+(e.ctrlKey?"\0":" ");else if(e.key==="Dead"&&e.code.startsWith("Key")){let h=e.code.slice(3,4);e.shiftKey||(h=h.toLowerCase()),r.key="\x1B"+h,r.cancel=!0}}else if(i&&!e.altKey&&!e.ctrlKey&&!e.shiftKey&&e.metaKey)e.keyCode===65&&(r.type=1);else if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&e.key.length===1)r.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":r.key="";break;case"Digit2":r.key="\0";break;case"Digit6":r.key="";break}break}return r}var wr=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){let t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e){let t=this._getNumpadKeyCode(e);if(t!==void 0)return t;let i=this._getModifierKeyCode(e);if(i!==void 0)return i;let s=this._functionalKeyCodes[e.key];if(s!==void 0)return s;if(e.shiftKey&&e.code){if(e.code.startsWith("Digit")&&e.code.length===6){let r=e.code.charAt(5);if(r>="0"&&r<="9")return r.charCodeAt(0)}if(e.code.startsWith("Key")&&e.code.length===4)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(e.key.length===1){let r=e.key.codePointAt(0);return r>=65&&r<=90?r+32:r}}_isModifierKey(e){return e.key==="Shift"||e.key==="Control"||e.key==="Alt"||e.key==="Meta"}_buildCsiLetterSequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1B["+e}_buildSs3Sequence(e,t,i,s){let r=s&&i!==1;if(t>0||r){let n="\x1B[1;"+(t>0?t:"1");return r&&(n+=":"+i),n+=e,n}return"\x1BO"+e}_buildCsiTildeSequence(e,t,i,s){let r=s&&i!==1,n="\x1B["+e;return(t>0||r)&&(n+=";"+(t>0?t:"1"),r&&(n+=":"+i)),n+="~",n}_buildCsiUSequence(e,t,i,s,r,n,o){let h=!!(r&2),l=!!(r&4),a="\x1B["+t,d;l&&e.shiftKey&&e.key.length===1&&!n&&!o&&(d=e.key.codePointAt(0),a+=":"+d);let c=r&16&&s!==3&&e.key.length===1&&!n&&!o&&!e.ctrlKey?e.key.codePointAt(0):void 0,u=h&&s!==1&&(s===3||c===void 0);return(i>0||u||c!==void 0)&&(a+=";",i>0?a+=i:u&&(a+="1"),u&&(a+=":"+s)),c!==void 0&&(a+=";"+c),a+="u",a}evaluate(e,t,i=1){let s={type:0,cancel:!1,key:void 0},r=this._encodeModifiers(e),n=this._isModifierKey(e),o=!!(t&2);if(!o&&i===3||n&&!(t&8)&&!o)return s;let h=this._csiLetterKeys[e.key];if(h)return s.key=this._buildCsiLetterSequence(h,r,i,o),s.cancel=!0,s;let l=this._ss3FunctionKeys[e.key];if(l)return s.key=this._buildSs3Sequence(l,r,i,o),s.cancel=!0,s;let a=this._csiTildeKeys[e.key];if(a!==void 0)return s.key=this._buildCsiTildeSequence(a,r,i,o),s.cancel=!0,s;let d=this._getKeyCode(e);if(d===void 0)return s;let c=this._functionalKeyCodes[e.key]!==void 0||this._getNumpadKeyCode(e)!==void 0,u=!1;return t&8||o?u=!0:t&1&&(d===27||d===127||d===13||d===9||d===32||c?u=!0:r>0&&(e.shiftKey&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1?u=!1:u=!0)),u?(s.key=this._buildCsiUSequence(e,d,r,i,t,c,n),s.cancel=!0):e.key.length===1&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&(s.key=e.key),s}static shouldUseProtocol(e){return e>0}},Vh=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){let t=this._codeToVk[e.code];return t!==void 0?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(e.key==="Enter")return 10;if(e.key==="Backspace")return 127}let t=this._keyToControlChar[e.key];if(t!==void 0)return t;if(e.key.length===1){let i=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(i>=65&&i<=90)return i-64;if(i>=97&&i<=122)return i-96}return i}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&(e.code==="ControlRight"?t|=4:t|=8),e.altKey&&(e.code==="AltRight"?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){let i=this._getVirtualKeyCode(e),s=this._getScanCode(e),r=this._getUnicodeChar(e),n=t?1:0,o=this._getControlKeyState(e);return{type:0,cancel:!0,key:`\x1B[${i};${s};${r};${n};${o};1_`}}},us=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??(this._win32InputMode=new Vh),this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??(this._kittyKeyboard=new wr),this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);let t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1):qh(e,this._coreService.decPrivateModes.applicationCursorKeys,Rt,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);let t=this._coreService.kittyKeyboard.flags;if(this.useKitty&&t&2)return this._getKittyKeyboard().evaluate(e,t,3)}get useKitty(){let e=this._coreService.kittyKeyboard.flags;return!!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard&&wr.shouldUseProtocol(e))}get useWin32InputMode(){return!!(this._optionsService.rawOptions.vtExtensions?.win32InputMode&&this._coreService.decPrivateModes.win32InputMode)}};us=K([w(0,Xe),w(1,he)],us);var Yh=class{constructor(...e){this._entries=new Map;for(let[t,i]of e)this.set(t,i)}set(e,t){let i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(let[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}},jh=class{constructor(){this._services=new Yh,this._services.set(bs,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){let i=co(e).sort((n,o)=>n.index-o.index),s=[];for(let n of i){let o=this._services.get(n.id);if(!o)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${n.id._id}.`);s.push(o)}let r=i.length>0?i[0].index:t.length;if(t.length!==r)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${r+1} conflicts with ${t.length} static arguments`);return new e(...t,...s)}},Xh={trace:0,debug:1,info:2,warn:3,error:4,off:5},Gh="xterm.js: ",_s=class extends I{constructor(e){super(),this._optionsService=e,this._logLevel=5,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=Xh[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;s--)this._array[this._getCyclicIndex(s+i.length)]=this._array[this._getCyclicIndex(s)];for(let s=0;sthis._maxLength){let s=this._length+i.length-this._maxLength;this._startIndex+=s,this._length=this._maxLength,this.onTrimEmitter.fire(s)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let r=t-1;r>=0;r--)this.set(e+r+i,this.get(e+r));let s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>22,i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):s]}set(t,i){this._data[t*D+1]=i[0],i[1].length>1?(this._combined[t]=i[1],this._data[t*D+0]=t|2097152|i[2]<<22):this._data[t*D+0]=i[1].charCodeAt(0)|i[2]<<22}getWidth(t){return this._data[t*D+0]>>22}hasWidth(t){return this._data[t*D+0]&12582912}getFg(t){return this._data[t*D+1]}getBg(t){return this._data[t*D+2]}hasContent(t){return this._data[t*D+0]&4194303}getCodePoint(t){let i=this._data[t*D+0];return i&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):i&2097151}isCombined(t){return this._data[t*D+0]&2097152}getString(t){let i=this._data[t*D+0];return i&2097152?this._combined[t]:i&2097151?Ye(i&2097151):""}isProtected(t){return this._data[t*D+2]&536870912}loadCell(t,i){return $t=t*D,i.content=this._data[$t+0],i.fg=this._data[$t+1],i.bg=this._data[$t+2],i.content&2097152&&(i.combinedData=this._combined[t]),i.bg&268435456&&(i.extended=this._extendedAttrs[t]),i}setCell(t,i){i.content&2097152&&(this._combined[t]=i.combinedData),i.bg&268435456&&(this._extendedAttrs[t]=i.extended),this._data[t*D+0]=i.content,this._data[t*D+1]=i.fg,this._data[t*D+2]=i.bg}setCellFromCodepoint(t,i,s,r){r.bg&268435456&&(this._extendedAttrs[t]=r.extended),this._data[t*D+0]=i|s<<22,this._data[t*D+1]=r.fg,this._data[t*D+2]=r.bg}addCodepointToCell(t,i,s){let r=this._data[t*D+0];r&2097152?this._combined[t]+=Ye(i):r&2097151?(this._combined[t]=Ye(r&2097151)+Ye(i),r&=-2097152,r|=2097152):r=i|1<<22,s&&(r&=-12582913,r|=s<<22),this._data[t*D+0]=r}insertCells(t,i,s){if(t%=this.length,t&&this.getWidth(t-1)===2&&this.setCellFromCodepoint(t-1,0,1,s),i=0;--r)this.setCell(t+i+r,this.loadCell(t+r,yr));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=s*4)this._data=new Uint32Array(this._data.buffer,0,s);else{let r=new Uint32Array(s);r.set(this._data),this._data=r}for(let r=this.length;r=t&&delete this._combined[h]}let n=Object.keys(this._extendedAttrs);for(let o=0;o=t&&delete this._extendedAttrs[h]}}return this.length=t,s*4*Bi=0;--t)if(this._data[t*D+0]&4194303)return t+(this._data[t*D+0]>>22);return 0}getNoBgTrimmedLength(){for(let t=this.length-1;t>=0;--t)if(this._data[t*D+0]&4194303||this._data[t*D+2]&50331648)return t+(this._data[t*D+0]>>22);return 0}copyCellsFrom(t,i,s,r,n){let o=t._data;if(n)for(let l=r-1;l>=0;l--){for(let a=0;a=i&&(this._combined[a-i+s]=t._combined[a])}}translateToString(t,i,s,r){i=i??0,s=s??this.length,t&&(s=Math.min(s,this.getTrimmedLength())),r&&(r.length=0);let n="";for(;i>22||1}return r&&r.push(i),n}};function Jh(e,t,i,s,r,n){let o=[];for(let h=0;h=h&&s0&&(y>c||d[y].getTrimmedLength()===0);y--)p++;p>0&&(o.push(h+d.length-p),o.push(p)),h+=d.length-1}return o}function Zh(e,t){let i=[],s=0,r=t[s],n=0;for(let o=0;oPt(e,a,t)).reduce((l,a)=>l+a),n=0,o=0,h=0;for(;hl&&(n-=l,o++);let a=e[o].getWidth(n-1)===2;a&&n--;let d=a?i-1:i;s.push(d),h+=d}return s}function Pt(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();let s=!e[t].hasContent(i-1)&&e[t].getWidth(i-1)===1,r=e[t+1].getWidth(0)===2;return s&&r?i-1:i}var Hn=class Wn{constructor(t){this.line=t,this.isDisposed=!1,this._disposables=[],this._id=Wn._nextId++,this._onDispose=this.register(new b),this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Fi(this._disposables),this._disposables.length=0)}register(t){return this._disposables.push(t),t}};Hn._nextId=1;var tl=Hn,X={},Qe=X.B;X[0]={"`":"\u25C6",a:"\u2592",b:"\u2409",c:"\u240C",d:"\u240D",e:"\u240A",f:"\xB0",g:"\xB1",h:"\u2424",i:"\u240B",j:"\u2518",k:"\u2510",l:"\u250C",m:"\u2514",n:"\u253C",o:"\u23BA",p:"\u23BB",q:"\u2500",r:"\u23BC",s:"\u23BD",t:"\u251C",u:"\u2524",v:"\u2534",w:"\u252C",x:"\u2502",y:"\u2264",z:"\u2265","{":"\u03C0","|":"\u2260","}":"\xA3","~":"\xB7"};X.A={"#":"\xA3"};X.B=void 0;X[4]={"#":"\xA3","@":"\xBE","[":"ij","\\":"\xBD","]":"|","{":"\xA8","|":"f","}":"\xBC","~":"\xB4"};X.C=X[5]={"[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X.R={"#":"\xA3","@":"\xE0","[":"\xB0","\\":"\xE7","]":"\xA7","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xA8"};X.Q={"@":"\xE0","[":"\xE2","\\":"\xE7","]":"\xEA","^":"\xEE","`":"\xF4","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xFB"};X.K={"@":"\xA7","[":"\xC4","\\":"\xD6","]":"\xDC","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xDF"};X.Y={"#":"\xA3","@":"\xA7","[":"\xB0","\\":"\xE7","]":"\xE9","`":"\xF9","{":"\xE0","|":"\xF2","}":"\xE8","~":"\xEC"};X.E=X[6]={"@":"\xC4","[":"\xC6","\\":"\xD8","]":"\xC5","^":"\xDC","`":"\xE4","{":"\xE6","|":"\xF8","}":"\xE5","~":"\xFC"};X.Z={"#":"\xA3","@":"\xA7","[":"\xA1","\\":"\xD1","]":"\xBF","{":"\xB0","|":"\xF1","}":"\xE7"};X.H=X[7]={"@":"\xC9","[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};X["="]={"#":"\xF9","@":"\xE0","[":"\xE9","\\":"\xE7","]":"\xEA","^":"\xEE",_:"\xE8","`":"\xF4","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xFB"};var Cr=4294967295,kr=class{constructor(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=Q.clone(),this.savedCharset=Qe,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=ke.fromCharData([0,"",1,0]),this._whitespaceCell=ke.fromCharData([0," ",1,32]),this._isClearing=!1,this._memoryCleanupQueue=new hi,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new br(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new ti),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new ti),this._whitespaceCell}getBlankLine(e,t){return new kt(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&eCr?Cr:t}fillViewportRows(e){if(this.lines.length===0){e??(e=Q);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new br(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let i=this.getNullCell(Q),s=0,r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+n+1?(this.ybase--,n++,this.ydisp>0&&this.ydisp--):this.lines.push(new kt(e,i)));else for(let o=this._rows;o>t;o--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(o),this.ybase=Math.max(this.ybase-o,0),this.ydisp=Math.max(this.ydisp-o,0),this.savedY=Math.max(this.savedY-o,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),n&&(this.y+=n),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let n=0;n0){let n=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,n)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend==="conpty"&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let i=this._optionsService.rawOptions.reflowCursorLine,s=Jh(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(Q),i);if(s.length>0){let r=Zh(this.lines,s);Qh(this.lines,r.layout),this._reflowLargerAdjustViewport(e,t,r.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){let s=this.getNullCell(Q),r=i;for(;r-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;o--){let h=this.lines.get(o);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;let l=[h];for(;h.isWrapped&&o>0;)h=this.lines.get(--o),l.unshift(h);if(!i){let R=this.ybase+this.y;if(R>=o&&R0&&(r.push({start:o+l.length+n,newLines:f}),n+=f.length),l.push(...f);let _=d.length-1,p=d[_];p===0&&(_--,p=d[_]);let y=l.length-c-1,E=a;for(;y>=0;){let R=Math.min(E,p);if(l[_]===void 0)break;if(l[_].copyCellsFrom(l[y],E-R,p-R,R,!0),p-=R,p===0&&(_--,p=d[_]),E-=R,E===0){y--;let x=Math.max(y,0);E=Pt(l,x,this._cols)}}for(let R=0;R0;)this.ybase===0?this.y0){let o=[],h=[];for(let p=0;p=0;p--)if(c&&c.start>a+u){for(let y=c.newLines.length-1;y>=0;y--)this.lines.set(p--,c.newLines[y]);p++,o.push({index:a+1,amount:c.newLines.length}),u+=c.newLines.length,c=r[++d]}else this.lines.set(p,h[a--]);let f=0;for(let p=o.length-1;p>=0;p--)o[p].index+=f,this.lines.onInsertEmitter.fire(o[p]),f+=o[p].amount;let _=Math.max(0,l+n-this.lines.maxLength);_>0&&this.lines.onTrimEmitter.fire(_)}}translateBufferLineToString(e,t,i=0,s){let r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=i,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(i=>{t.line>=i.index&&(t.line+=i.amount)})),t.register(this.lines.onDelete(i=>{t.line>=i.index&&t.linei.index&&(t.line-=i.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}},il=class extends I{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new b),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new kr(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new kr(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}},zn=2,Kn=1,fs=class extends I{constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,zn),this.rows=Math.max(e.rawOptions.rows||0,Kn),this.buffers=this._register(new il(e,this)),this._register(this.buffers.onBufferActivate(t=>{this._onScroll.fire(t.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let i=this.buffer,s;s=this._cachedBlankLine,(!s||s.length!==this.cols||s.getFg(0)!==e.fg||s.getBg(0)!==e.bg)&&(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;let r=i.ybase+i.scrollTop,n=i.ybase+i.scrollBottom;if(i.scrollTop===0){let o=i.lines.isFull;n===i.lines.length-1?o?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(n+1,0,s.clone()),o?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{let o=n-r+1;i.lines.shiftElements(r+1,o-1,-1),i.lines.set(n,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){let i=this.buffer;if(e<0){if(i.ydisp===0)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);let s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};fs=K([w(0,he)],fs);var rt={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:Rt,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",overviewRuler:{},quirks:{},vtExtensions:{}},sl=["normal","bold","100","200","300","400","500","600","700","800","900"],rl=class extends I{constructor(e){super(),this._onOptionChange=this._register(new b),this.onOptionChange=this._onOptionChange.event;let t={...rt};for(let i in e)if(i in t)try{let s=e[i];t[i]=this._sanitizeAndValidateOption(i,s)}catch(s){console.error(s)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(q(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{e.indexOf(i)!==-1&&t()})}_setupOptions(){let e=i=>{if(!(i in rt))throw new Error(`No option with key "${i}"`);return this.rawOptions[i]},t=(i,s)=>{if(!(i in rt))throw new Error(`No option with key "${i}"`);s=this._sanitizeAndValidateOption(i,s),this.rawOptions[i]!==s&&(this.rawOptions[i]=s,this._onOptionChange.fire(i))};for(let i in this.rawOptions){let s={get:e.bind(this,i),set:t.bind(this,i)};Object.defineProperty(this.options,i,s)}}_sanitizeAndValidateOption(e,t){switch(e){case"cursorStyle":if(t||(t=rt[e]),!nl(t))throw new Error(`"${t}" is not a valid value for ${e}`);break;case"wordSeparator":t||(t=rt[e]);break;case"fontWeight":case"fontWeightBold":if(typeof t=="number"&&1<=t&&t<=1e3)break;t=sl.includes(t)?t:rt[e];break;case"cursorWidth":t=Math.floor(t);case"lineHeight":case"tabStopWidth":if(t<1)throw new Error(`${e} cannot be less than 1, value: ${t}`);break;case"minimumContrastRatio":t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case"scrollback":if(t=Math.min(t,4294967295),t<0)throw new Error(`${e} cannot be less than 0, value: ${t}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(t<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case"rows":case"cols":if(!t&&t!==0)throw new Error(`${e} must be numeric, value: ${t}`);break;case"windowsPty":t=t??{};break}return t}};function nl(e){return e==="block"||e==="underline"||e==="bar"}function xt(e,t=5){if(typeof e!="object")return e;let i=Array.isArray(e)?[]:{};for(let s in e)i[s]=t<=1?e[s]:e[s]&&xt(e[s],t-1);return i}var xr=Object.freeze({insertMode:!1}),Er=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0}),Br=()=>({flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}),ps=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new b),this.onData=this._onData.event,this._onUserInput=this._register(new b),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new b),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=xt(xr),this.decPrivateModes=xt(Er),this.kittyKeyboard=Br()}reset(){this.modes=xt(xr),this.decPrivateModes=xt(Er),this.kittyKeyboard=Br()}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(s=>s.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(t=>t.charCodeAt(0))),this._onBinary.fire(e))}};ps=K([w(0,ce),w(1,Zr),w(2,he)],ps);var Dr={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>e.button===4||e.action!==1?!1:(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>e.action!==32},DRAG:{events:23,restrict:e=>!(e.action===32&&e.button===3)},ANY:{events:31,restrict:e=>!0}};function Di(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return e.button===4?(i|=64,i|=e.action):(i|=e.button&3,e.button&4&&(i|=64),e.button&8&&(i|=128),e.action===32?i|=32:e.action===0&&!t&&(i|=3)),i}var Ri=String.fromCharCode,Rr={DEFAULT:e=>{let t=[Di(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`\x1B[M${Ri(t[0])}${Ri(t[1])}${Ri(t[2])}`},SGR:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Di(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{let t=e.action===0&&e.button!==4?"m":"M";return`\x1B[<${Di(e,!0)};${e.x};${e.y}${t}`}},gs=class extends I{constructor(e,t,i){super(),this._bufferService=e,this._coreService=t,this._optionsService=i,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new b),this.onProtocolChange=this._onProtocolChange.event;for(let s of Object.keys(Dr))this.addProtocol(s,Dr[s]);for(let s of Object.keys(Rr))this.addEncoding(s,Rr[s]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,i){if(e.deltaY===0||e.shiftKey||t===void 0||i===void 0)return 0;let s=t/i,r=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(r/=s+0,Math.abs(e.deltaY)<50&&(r*=.3),this._wheelPartialScroll+=r,r=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows||e.button===4&&e.action===32||e.button===3&&e.action!==32||e.button!==4&&(e.action===2||e.action===3)||(e.col++,e.row++,e.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._activeEncoding==="SGR_PIXELS"))||!this._protocols[this._activeProtocol].restrict(e))return!1;let t=this._encodings[this._activeEncoding](e);return t&&(this._activeEncoding==="DEFAULT"?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(e&1),up:!!(e&2),drag:!!(e&4),move:!!(e&8),wheel:!!(e&16)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x||e.y!==t.y)return!1}else if(e.col!==t.col||e.row!==t.row)return!1;return!(e.button!==t.button||e.action!==t.action||e.ctrl!==t.ctrl||e.alt!==t.alt||e.shift!==t.shift)}};gs=K([w(0,ce),w(1,Xe),w(2,he)],gs);var Mi=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],ol=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],j;function al(e,t){let i=0,s=t.length-1,r;if(et[s][1])return!1;for(;s>=i;)if(r=i+s>>1,e>t[r][1])i=r+1;else if(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),s=i===0&&t!==0;if(s){let r=et.extractWidth(t);r===0?s=!1:r>i&&(i=r)}return et.createPropertyValue(0,i,s)}},et=class Qt{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new b,this.onChange=this._onChange.event;let t=new hl;this.register(t),this._active=t.version,this._activeProvider=t}static extractShouldJoin(t){return(t&1)!==0}static extractWidth(t){return t>>1&3}static extractCharKind(t){return t>>3}static createPropertyValue(t,i,s=!1){return(t&16777215)<<3|(i&3)<<1|(s?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(t){if(!this._providers[t])throw new Error(`unknown Unicode version "${t}"`);this._active=t,this._activeProvider=this._providers[t],this._onChange.fire(t)}register(t){this._providers[t.version]=t}wcwidth(t){return this._activeProvider.wcwidth(t)}getStringCellWidth(t){let i=0,s=0,r=t.length;for(let n=0;n=r)return i+this.wcwidth(o);let a=t.charCodeAt(n);56320<=a&&a<=57343?o=(o-55296)*1024+a-56320+65536:i+=this.wcwidth(a)}let h=this.charProperties(o,s),l=Qt.extractWidth(h);Qt.extractShouldJoin(h)&&(l-=Qt.extractWidth(s)),i+=l,s=h}return i}charProperties(t,i){return this._activeProvider.charProperties(t,i)}},ll=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}};function Mr(e){let t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1)?.get(e.cols-1),i=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);i&&t&&(i.isWrapped=t[3]!==0&&t[3]!==32)}var mt=2147483647,cl=256,$n=class vs{constructor(t=32,i=32){if(this.maxLength=t,this.maxSubParamsLength=i,i>cl)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(t),this.length=0,this._subParams=new Int32Array(i),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(t),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let i=new vs;if(!t.length)return i;for(let s=Array.isArray(t[0])?1:0;s>8,r=this._subParamsIdx[i]&255;r-s>0&&t.push(Array.prototype.slice.call(this._subParams,s,r))}return t}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(t){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=t>mt?mt:t}addSubParam(t){if(this._digitIsSub=!0,!!this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(t<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=t>mt?mt:t,this._subParamsIdx[this.length-1]++}}hasSubParams(t){return(this._subParamsIdx[t]&255)-(this._subParamsIdx[t]>>8)>0}getSubParams(t){let i=this._subParamsIdx[t]>>8,s=this._subParamsIdx[t]&255;return s-i>0?this._subParams.subarray(i,s):null}getSubParamsAll(){let t={};for(let i=0;i>8,r=this._subParamsIdx[i]&255;r-s>0&&(t[i]=this._subParams.slice(s,r))}return t}addDigit(t){let i;if(this._rejectDigits||!(i=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let s=this._digitIsSub?this._subParams:this.params,r=s[i-1];s[i-1]=~r?Math.min(r*10+t,mt):t}},St=[],dl=class{constructor(){this._state=0,this._active=St,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=St}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=St,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||St,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",ht(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){if(this._state!==3){if(this._state===1)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=St,this._id=-1,this._state=0}}},Un=class qn{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=ht(t,i,s),this._data.length>qn._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};Un._payloadLimit=1e7;var ge=Un,wt=[],ul=class{constructor(){this._handlers=Object.create(null),this._active=wt,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=wt}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=wt,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||wt,!this._active.length)this._handlerFb(this._ident,"HOOK",t);else for(let i=this._active.length-1;i>=0;i--)this._active[i].hook(t)}put(e,t,i){if(!this._active.length)this._handlerFb(this._ident,"PUT",ht(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}unhook(e,t=!0){if(!this._active.length)this._handlerFb(this._ident,"UNHOOK",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].unhook(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}this._active=wt,this._ident=0}},Et=new $n;Et.addParam(0);var Vn=class Yn{constructor(t){this._handler=t,this._data="",this._params=Et,this._hitLimit=!1}hook(t){this._params=t.length>1||t.params[0]?t.clone():Et,this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=ht(t,i,s),this._data.length>Yn._payloadLimit&&(this._data="",this._hitLimit=!0))}unhook(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data,this._params),i instanceof Promise))return i.then(s=>(this._params=Et,this._data="",this._hitLimit=!1,s));return this._params=Et,this._data="",this._hitLimit=!1,i}};Vn._payloadLimit=1e7;var Lr=Vn,nt=[],_l=class{constructor(){this._state=0,this._active=nt,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){var s;(s=this._handlers)[e]??(s[e]=[]);let i=this._handlers[e];return i.push(t),{dispose:()=>{let r=i.indexOf(t);r!==-1&&i.splice(r,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=nt}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=nt,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||nt,!this._active.length)this._handlerFb(this._id,"START");else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,i){if(!this._active.length)this._handlerFb(this._id,"PUT",ht(e,t,i));else for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i)}start(){this.reset(),this._state=1}put(e,t,i){this._state!==3&&(this._state===1&&t0&&this._put(e,t,i))}end(e,t=!0){if(this._state!==0){if(this._state!==3){if(this._state===1){this._active=nt,this._id=-1,this._state=0;return}if(!this._active.length)this._handlerFb(this._id,"END",e);else{let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&i===!1){for(;s>=0&&(i=this._active[s].end(e),i!==!0);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}}this._active=nt,this._id=-1,this._state=0}}},jn=class Xn{constructor(t){this._handler=t,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,i,s){this._hitLimit||(this._data+=ht(t,i,s),this._data.length>Xn._payloadLimit&&(this._data="",this._hitLimit=!0))}end(t){let i=!1;if(this._hitLimit)i=!1;else if(t&&(i=this._handler(this._data),i instanceof Promise))return i.then(s=>(this._data="",this._hitLimit=!1,s));return this._data="",this._hitLimit=!1,i}};jn._payloadLimit=1e7;var fl=jn,pl=class{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rh),i=(o,h)=>t.slice(o,h),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));let n=i(0,15);e.setDefault(1,0),e.addMany(s,0,2,0);for(let o of n)e.addMany([24,26,153,154],o,3,0),e.addMany(i(128,144),o,3,0),e.addMany(i(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158],o,0,7),e.add(159,o,15,14),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,15,14),e.addMany(s,14,16,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany([27,156,24,26],14,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(28,32),9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(i(28,32),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(28,32),10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(28,32),12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(ue,0,2,0),e.add(ue,8,5,8),e.add(ue,6,0,6),e.add(ue,11,0,11),e.add(ue,13,13,13),e.add(ue,14,16,14),e})(),vl=class extends I{constructor(e=gl){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new $n,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(t,i,s)=>{},this._executeHandlerFb=t=>{},this._csiHandlerFb=(t,i)=>{},this._escHandlerFb=t=>{},this._errorHandlerFb=t=>t,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(q(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new dl),this._dcsParser=this._register(new ul),this._apcParser=this._register(new _l),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let r=0;rn||n>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=n}}if(e.final.length!==1)throw new Error("final must be a single byte");let s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){var r;let i=this._identifier(e,[48,126]);(r=this._escHandlers)[i]??(r[i]=[]);let s=this._escHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){var r;let i=this._identifier(e);(r=this._csiHandlers)[i]??(r[i]=[]);let s=this._csiHandlers[i];return s.push(t),{dispose:()=>{let n=s.indexOf(t);n!==-1&&s.splice(n,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return this._apcParser.registerHandler(e,t)}clearApcHandler(e){this._apcParser.clearHandler(e)}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s=0,r=0,n=0,o;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(i===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");let h=this._parseStack.handlers,l=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](this._params),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 4:if(i===!1&&l>-1){for(;l>=0&&(o=h[l](),o!==!0);l--)if(o instanceof Promise)return this._parseStack.handlerPos=l,o}this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(s!==24&&s!==26,i),o)return o;s===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&255}for(let h=n;h>8){case 2:for(let u=h+1;;++u){if(u>=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=t||(s=e[u])<32||s>126&&s=0&&(o=l[a](this._params),o!==!0);a--)if(o instanceof Promise)return this._preserveStack(3,l,a,r,h),o;a<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}while(++h47&&s<60);h--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:let d=this._escHandlers[this._collect<<8|s],c=d?d.length-1:-1;for(;c>=0&&(o=d[c](),o!==!0);c--)if(o instanceof Promise)return this._preserveStack(4,d,c,r,h),o;c<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let u=h+1;;++u)if(u>=t||(s=e[u])===24||s===26||s===27||s>127&&s=t||(s=e[u])<32||s>127&&s=t||(s=e[u])===24||s===26||s===27||s===156||s>127&&s>4:n>>8}return s}}function Li(e,t){let i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}function wl(e,t=16){let[i,s,r]=e;return`rgb:${Li(i,t)}/${Li(s,t)}/${Li(r,t)}`}var bl="6.1.0-beta.141",yl={"(":0,")":1,"*":2,"+":3,"-":1,".":2},Ve=131072,Tr=10;function Ar(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var Or=5e3,Ir=0,Cl=class extends I{constructor(e,t,i,s,r,n,o,h,l=new vl){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=n,this._coreMouseService=o,this._unicodeService=h,this._parser=l,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new ho,this._utf8Decoder=new lo,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone(),this._onRequestBell=this._register(new b),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new b),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new b),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new b),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new b),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new b),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new b),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new b),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new b),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new b),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new b),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new ms(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(a=>this._activeBuffer=a.activeBuffer)),this._parser.setCsiHandlerFallback((a,d)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(a),params:d.toArray()})}),this._parser.setEscHandlerFallback(a=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(a)})}),this._parser.setExecuteHandlerFallback(a=>{this._logService.debug("Unknown EXECUTE code: ",{code:a})}),this._parser.setOscHandlerFallback((a,d,c)=>{this._logService.debug("Unknown OSC code: ",{identifier:a,action:d,data:c})}),this._parser.setDcsHandlerFallback((a,d,c)=>{d==="HOOK"&&(c=c.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(a),action:d,payload:c})}),this._parser.setPrintHandler((a,d,c)=>this.print(a,d,c)),this._parser.registerCsiHandler({final:"@"},a=>this.insertChars(a)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},a=>this.scrollLeft(a)),this._parser.registerCsiHandler({final:"A"},a=>this.cursorUp(a)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},a=>this.scrollRight(a)),this._parser.registerCsiHandler({final:"B"},a=>this.cursorDown(a)),this._parser.registerCsiHandler({final:"C"},a=>this.cursorForward(a)),this._parser.registerCsiHandler({final:"D"},a=>this.cursorBackward(a)),this._parser.registerCsiHandler({final:"E"},a=>this.cursorNextLine(a)),this._parser.registerCsiHandler({final:"F"},a=>this.cursorPrecedingLine(a)),this._parser.registerCsiHandler({final:"G"},a=>this.cursorCharAbsolute(a)),this._parser.registerCsiHandler({final:"H"},a=>this.cursorPosition(a)),this._parser.registerCsiHandler({final:"I"},a=>this.cursorForwardTab(a)),this._parser.registerCsiHandler({final:"J"},a=>this.eraseInDisplay(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},a=>this.eraseInDisplay(a,!0)),this._parser.registerCsiHandler({final:"K"},a=>this.eraseInLine(a,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},a=>this.eraseInLine(a,!0)),this._parser.registerCsiHandler({final:"L"},a=>this.insertLines(a)),this._parser.registerCsiHandler({final:"M"},a=>this.deleteLines(a)),this._parser.registerCsiHandler({final:"P"},a=>this.deleteChars(a)),this._parser.registerCsiHandler({final:"S"},a=>this.scrollUp(a)),this._parser.registerCsiHandler({final:"T"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"X"},a=>this.eraseChars(a)),this._parser.registerCsiHandler({final:"Z"},a=>this.cursorBackwardTab(a)),this._parser.registerCsiHandler({final:"^"},a=>this.scrollDown(a)),this._parser.registerCsiHandler({final:"`"},a=>this.charPosAbsolute(a)),this._parser.registerCsiHandler({final:"a"},a=>this.hPositionRelative(a)),this._parser.registerCsiHandler({final:"b"},a=>this.repeatPrecedingCharacter(a)),this._parser.registerCsiHandler({final:"c"},a=>this.sendDeviceAttributesPrimary(a)),this._parser.registerCsiHandler({prefix:">",final:"c"},a=>this.sendDeviceAttributesSecondary(a)),this._parser.registerCsiHandler({final:"d"},a=>this.linePosAbsolute(a)),this._parser.registerCsiHandler({final:"e"},a=>this.vPositionRelative(a)),this._parser.registerCsiHandler({final:"f"},a=>this.hVPosition(a)),this._parser.registerCsiHandler({final:"g"},a=>this.tabClear(a)),this._parser.registerCsiHandler({final:"h"},a=>this.setMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"h"},a=>this.setModePrivate(a)),this._parser.registerCsiHandler({final:"l"},a=>this.resetMode(a)),this._parser.registerCsiHandler({prefix:"?",final:"l"},a=>this.resetModePrivate(a)),this._parser.registerCsiHandler({final:"m"},a=>this.charAttributes(a)),this._parser.registerCsiHandler({final:"n"},a=>this.deviceStatus(a)),this._parser.registerCsiHandler({prefix:"?",final:"n"},a=>this.deviceStatusPrivate(a)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},a=>this.softReset(a)),this._parser.registerCsiHandler({prefix:">",final:"q"},a=>this.sendXtVersion(a)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},a=>this.setCursorStyle(a)),this._parser.registerCsiHandler({final:"r"},a=>this.setScrollRegion(a)),this._parser.registerCsiHandler({final:"s"},a=>this.saveCursor(a)),this._parser.registerCsiHandler({final:"t"},a=>this.windowOptions(a)),this._parser.registerCsiHandler({final:"u"},a=>this.restoreCursor(a)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},a=>this.insertColumns(a)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},a=>this.deleteColumns(a)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},a=>this.selectProtected(a)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},a=>this.requestMode(a,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},a=>this.requestMode(a,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},a=>this.kittyKeyboardSet(a)),this._parser.registerCsiHandler({prefix:"?",final:"u"},a=>this.kittyKeyboardQuery(a)),this._parser.registerCsiHandler({prefix:">",final:"u"},a=>this.kittyKeyboardPush(a)),this._parser.registerCsiHandler({prefix:"<",final:"u"},a=>this.kittyKeyboardPop(a)),this._parser.setExecuteHandler("\x07",()=>this.bell()),this._parser.setExecuteHandler(` +`,()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler(" ",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("\x84",()=>this.index()),this._parser.setExecuteHandler("\x85",()=>this.nextLine()),this._parser.setExecuteHandler("\x88",()=>this.tabSet()),this._parser.registerOscHandler(0,new ge(a=>(this.setTitle(a),this.setIconName(a),!0))),this._parser.registerOscHandler(1,new ge(a=>this.setIconName(a))),this._parser.registerOscHandler(2,new ge(a=>this.setTitle(a))),this._parser.registerOscHandler(4,new ge(a=>this.setOrReportIndexedColor(a))),this._parser.registerOscHandler(8,new ge(a=>this.setHyperlink(a))),this._parser.registerOscHandler(10,new ge(a=>this.setOrReportFgColor(a))),this._parser.registerOscHandler(11,new ge(a=>this.setOrReportBgColor(a))),this._parser.registerOscHandler(12,new ge(a=>this.setOrReportCursorColor(a))),this._parser.registerOscHandler(104,new ge(a=>this.restoreIndexedColor(a))),this._parser.registerOscHandler(110,new ge(a=>this.restoreFgColor(a))),this._parser.registerOscHandler(111,new ge(a=>this.restoreBgColor(a))),this._parser.registerOscHandler(112,new ge(a=>this.restoreCursorColor(a))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(let a in X)this._parser.registerEscHandler({intermediates:"(",final:a},()=>this.selectCharset("("+a)),this._parser.registerEscHandler({intermediates:")",final:a},()=>this.selectCharset(")"+a)),this._parser.registerEscHandler({intermediates:"*",final:a},()=>this.selectCharset("*"+a)),this._parser.registerEscHandler({intermediates:"+",final:a},()=>this.selectCharset("+"+a)),this._parser.registerEscHandler({intermediates:"-",final:a},()=>this.selectCharset("-"+a)),this._parser.registerEscHandler({intermediates:".",final:a},()=>this.selectCharset("."+a)),this._parser.registerEscHandler({intermediates:"/",final:a},()=>this.selectCharset("/"+a));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(a=>(this._logService.error("Parsing error: ",a),a)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new Lr((a,d)=>this.requestStatusString(a,d)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=3){let t,i=new Promise((s,r)=>{t=setTimeout(()=>r("#SLOW_TIMEOUT"),Or)});Promise.race([e,i]).then(()=>{t!==void 0&&clearTimeout(t)},s=>{if(t!==void 0&&clearTimeout(t),s!=="#SLOW_TIMEOUT")throw s;console.warn(`async parser handler taking longer than ${Or} ms`)})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,n=0,o=this._parseStack.paused;if(o){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>Ve&&(n=this._parseStack.position+Ve)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e=="string"?` "${e}"`:` "${Array.prototype.map.call(e,a=>String.fromCharCode(a)).join("")}"`}`),this._logService.logLevel===0&&this._logService.trace("parsing data (codes)",typeof e=="string"?e.split("").map(a=>a.charCodeAt(0)):e),this._parseBuffer.lengthVe)for(let a=n;a0&&c.getWidth(this._activeBuffer.x-1)===2&&c.setCellFromCodepoint(this._activeBuffer.x-1,0,1,d);let u=this._parser.precedingJoinState;for(let f=t;fh){if(l){let E=c,A=this._activeBuffer.x-y;if(this._activeBuffer.x=y,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),c=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!c)return;for(y>0&&c instanceof kt&&c.copyCellsFrom(E,A,0,y,!1);A=0;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d);continue}if(a&&(c.insertCells(this._activeBuffer.x,r-y,this._activeBuffer.getNullCell(d)),c.getWidth(h-1)===2&&c.setCellFromCodepoint(h-1,0,1,d)),c.setCellFromCodepoint(this._activeBuffer.x++,s,r,d),r>0)for(;--r;)c.setCellFromCodepoint(this._activeBuffer.x++,0,0,d)}this._parser.precedingJoinState=u,this._activeBuffer.x0&&c.getWidth(this._activeBuffer.x)===0&&!c.hasContent(this._activeBuffer.x)&&c.setCellFromCodepoint(this._activeBuffer.x,0,1,d),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final==="t"&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,i=>Ar(i.params[0],this._optionsService.rawOptions.windowOptions)?t(i):!0):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new Lr(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new ge(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new fl(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){let n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n&&(n.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(n.isWrapped=!1))}_resetBufferLine(e,t=!1){let i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let i;switch(e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);i=this._bufferService.cols){let r=this._activeBuffer.lines.get(i+1);r&&(r.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+i)?.getTrimmedLength(););for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let s=this._activeBuffer.lines.length-this._bufferService.rows;s>0&&(this._activeBuffer.lines.trimStart(s),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-s,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-s,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let l=h;for(let a=1;a0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("\x1B[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("\x1B[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("\x1B[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("\x1B[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("\x1B[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`\x1BP>|xterm.js(${bl})\x1B\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(p[p.NOT_RECOGNIZED=0]="NOT_RECOGNIZED",p[p.SET=1]="SET",p[p.RESET=2]="RESET",p[p.PERMANENTLY_SET=3]="PERMANENTLY_SET",p[p.PERMANENTLY_RESET=4]="PERMANENTLY_RESET"))(void 0||(i={}));let s=this._coreService.decPrivateModes,{activeProtocol:r,activeEncoding:n}=this._coreMouseService,o=this._coreService,{buffers:h,cols:l}=this._bufferService,{active:a,alt:d}=h,c=this._optionsService.rawOptions,u=(p,y)=>(o.triggerDataEvent(`\x1B[${t?"":"?"}${p};${y}$y`),!0),f=p=>p?1:2,_=e.params[0];return t?_===2?u(_,4):_===4?u(_,f(o.modes.insertMode)):_===12?u(_,3):_===20?u(_,f(c.convertEol)):u(_,0):_===1?u(_,f(s.applicationCursorKeys)):_===3?u(_,c.windowOptions.setWinLines?l===80?2:l===132?1:0:0):_===6?u(_,f(s.origin)):_===7?u(_,f(s.wraparound)):_===8?u(_,3):_===9?u(_,f(r==="X10")):_===12?u(_,f(c.cursorBlink)):_===25?u(_,f(!o.isCursorHidden)):_===45?u(_,f(s.reverseWraparound)):_===66?u(_,f(s.applicationKeypad)):_===67?u(_,4):_===1e3?u(_,f(r==="VT200")):_===1002?u(_,f(r==="DRAG")):_===1003?u(_,f(r==="ANY")):_===1004?u(_,f(s.sendFocus)):_===1005?u(_,4):_===1006?u(_,f(n==="SGR")):_===1015?u(_,4):_===1016?u(_,f(n==="SGR_PIXELS")):_===1048?u(_,1):_===47||_===1047||_===1049?u(_,f(a===d)):_===2004?u(_,f(s.bracketedPasteMode)):_===2026?u(_,f(s.synchronizedOutput)):_===9001&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?u(_,f(s.win32InputMode)):u(_,0)}_updateAttrColor(e,t,i,s,r){return t===2?(e|=50331648,e&=-16777216,e|=Tt.fromColorRGB([i,s,r])):t===5&&(e&=-50331904,e|=33554432|i&255),e}_extractColor(e,t,i){let s=[0,0,-1,0,0,0],r=0,n=0;do{if(s[n+r]=e.params[t+n],e.hasSubParams(t+n)){let o=e.getSubParams(t+n),h=0;do s[1]===5&&(r=1),s[n+h+1+r]=o[h];while(++h=2||s[1]===2&&n+r>=5)break;s[1]&&(r=1)}while(++n+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=Q.fg,e.bg=Q.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,i,s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-50331904,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-50331904,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-50331904,s.fg|=16777216|i-90|8):i>=100&&i<=107?(s.bg&=-50331904,s.bg|=16777216|i-100|8):i===0?this._processSGR0(s):i===1?s.fg|=134217728:i===3?s.bg|=67108864:i===4?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):i===5?s.fg|=536870912:i===7?s.fg|=67108864:i===8?s.fg|=1073741824:i===9?s.fg|=2147483648:i===2?s.bg|=134217728:i===21?this._processUnderline(2,s):i===22?(s.fg&=-134217729,s.bg&=-134217729):i===23?s.bg&=-67108865:i===24?(s.fg&=-268435457,this._processUnderline(0,s)):i===25?s.fg&=-536870913:i===27?s.fg&=-67108865:i===28?s.fg&=-1073741825:i===29?s.fg&=2147483647:i===39?(s.fg&=-67108864,s.fg|=Q.fg&16777215):i===49?(s.bg&=-67108864,s.bg|=Q.bg&16777215):i===38||i===48||i===58?r+=this._extractColor(e,r,s):i===53?s.bg|=1073741824:i===55?s.bg&=-1073741825:i===221&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.fg&=-134217729:i===222&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??!0)?s.bg&=-134217729:i===59?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("\x1B[0n");break;case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[${t};${i}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let t=this._activeBuffer.y+1,i=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`\x1B[?${t};${i}R`);break;case 15:break;case 25:break;case 26:break;case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??!0)&&this._onRequestColorSchemeQuery.fire();break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=Q.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar";break}let i=t%2===1;this._coreService.decPrivateModes.cursorBlink=i}return!0}setScrollRegion(e){let t=e.params[0]||1,i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||i===0)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!Ar(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`\x1B[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>Tr&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>Tr&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let t=0;t1;){let s=i.shift(),r=i.shift();if(/^\d+$/.exec(s)){let n=parseInt(s);if(Nr(n))if(r==="?")t.push({type:0,index:n});else{let o=Pr(r);o&&t.push({type:1,index:n,color:o})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(";");if(t===-1)return!0;let i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):i.trim()?!1:this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let i=e.split(":"),s,r=i.findIndex(n=>n.startsWith("id="));return r!==-1&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let i=e.split(";");for(let s=0;s=this._specialColors.length);++s,++t)if(i[s]==="?")this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let r=Pr(i[s]);r&&this._onColor.fire([{type:1,index:this._specialColors[t],color:r}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],i=e.split(";");for(let s=0;s=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=Q.clone(),this._eraseAttrDataInternal=Q.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new ke;e.content=1<<22|69,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`\x1B${o}\x1B\\`),!0),s=this._bufferService.buffer,r=this._optionsService.rawOptions;return i(e==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:e==='"p'?'P1$r61;1"p':e==="r"?`P1$r${s.scrollTop+1};${s.scrollBottom+1}r`:e==="m"?"P1$r0m":e===" q"?`P1$r${{block:2,underline:4,bar:6}[r.cursorStyle]-(r.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t;break}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`\x1B[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;let t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let r=0;r0;r++)i.flags=s.pop();return s.length===0&&t>0&&(i.flags=0),!0}},ms=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(Ir=e,e=t,t=Ir),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};ms=K([w(0,ce)],ms);function Nr(e){return 0<=e&&e<256}var kl=5e7,Fr=12,xl=50,El=class extends I{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._isSyncWriting)return;this._isSyncWriting=!0;let e;for(;e=this._writeBuffer.shift();){this._action(e);let t=this._callbacks.shift();t&&t()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1}writeSync(e,t){if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let i;for(;i=this._writeBuffer.shift();){this._action(i);let s=this._callbacks.shift();s&&s()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>kl)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){let i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let s=this._writeBuffer[this._bufferOffset],r=this._action(s,t);if(r){let o=h=>performance.now()-i>=Fr?setTimeout(()=>this._innerWrite(0,h)):this._innerWrite(i,h);r.catch(h=>(queueMicrotask(()=>{throw h}),Promise.resolve(!1))).then(o);return}let n=this._callbacks[this._bufferOffset];if(n&&n(),this._bufferOffset++,this._pendingData-=s.length,performance.now()-i>=Fr)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>xl&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}},Ss=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){let t=this._bufferService.buffer;if(e.id===void 0){let h=t.addMarker(t.ybase+t.y),l={data:e,id:this._nextId++,lines:[h]};return h.onDispose(()=>this._removeMarkerFromLink(l,h)),this._dataByLinkId.set(l.id,l),l.id}let i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;let n=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[n]};return n.onDispose(()=>this._removeMarkerFromLink(o,n)),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){let i=this._dataByLinkId.get(e);if(i&&i.lines.every(s=>s.line!==t)){let s=this._bufferService.buffer.addMarker(t);i.lines.push(s),s.onDispose(()=>this._removeMarkerFromLink(i,s))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){let i=e.lines.indexOf(t);i!==-1&&(e.lines.splice(i,1),e.lines.length===0&&(e.data.id!==void 0&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};Ss=K([w(0,ce)],Ss);var Hr=!1,Bl=class extends I{constructor(e){super(),this._windowsWrappingHeuristics=this._register(new at),this._onBinary=this._register(new b),this.onBinary=this._onBinary.event,this._onData=this._register(new b),this.onData=this._onData.event,this._onLineFeed=this._register(new b),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new b),this.onRender=this._onRender.event,this._onResize=this._register(new b),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new b),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new b),this._instantiationService=new jh,this.optionsService=this._register(new rl(e)),this._instantiationService.setService(he,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(fs)),this._instantiationService.setService(ce,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(_s)),this._instantiationService.setService(Zr,this._logService),this.coreService=this._register(this._instantiationService.createInstance(ps)),this._instantiationService.setService(Xe,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(gs)),this._instantiationService.setService(Jr,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(et)),this._instantiationService.setService(fo,this.unicodeService),this._charsetService=this._instantiationService.createInstance(ll),this._instantiationService.setService(_o,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(Ss),this._instantiationService.setService(Qr,this._oscLinkService),this._inputHandler=this._register(new Cl(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(_e.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(_e.forward(this._bufferService.onResize,this._onResize)),this._register(_e.forward(this.coreService.onData,this._onData)),this._register(_e.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new El((t,i)=>this._inputHandler.parse(t,i))),this._register(_e.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new b),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!Hr&&(this._logService.warn("writeSync is unreliable and will be removed soon."),Hr=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,zn),t=Math.max(t,Kn),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.buildNumber!==void 0&&t.buildNumber!==void 0&&(e=t.backend==="conpty"&&t.buildNumber<21376),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Mr.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>(Mr(this._bufferService),!1))),this._windowsWrappingHeuristics.value=q(()=>{for(let t of e)t.dispose()})}}},U=0,Dl=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new hi,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new hi,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){let e=this._insertedValues.sort((r,n)=>this._getKey(r)-this._getKey(n)),t=0,i=0,s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),this._array.length===0)return!1;let t=this._getKey(e);if(t===void 0||(U=this._search(t),U===-1)||this._getKey(this._array[U])!==t)return!1;do if(this._array[U]===e)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(U),!0;while(++Ur-n),t=0,i=new Array(this._array.length-e.length),s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(U=this._search(e),!(U<0||U>=this._array.length)&&this._getKey(this._array[U])===e))do yield this._array[U];while(++U=this._array.length)&&this._getKey(this._array[U])===e))do t(this._array[U]);while(++U=t;){let s=t+i>>1,r=this._getKey(this._array[s]);if(r>e)i=s-1;else if(r0&&this._getKey(this._array[s-1])===e;)s--;return s}}return t}},Pi=0,Wr=0,Ti=0,zr=0,Rl=class extends I{constructor(){super(),this._decorations=new Dl(e=>e?.marker.line),this._onDecorationRegistered=this._register(new b),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new b),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register(q(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new Ml(e);if(t){let i=t.marker.onDispose(()=>t.dispose()),s=t.onDispose(()=>{s.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),i.dispose())});this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,i){let s=0,r=0,n=0,o=0;for(let h of this._decorations.values())n=h.marker.line,o=n+(h.options.height??1),!(t=o)&&(s=h.options.x??0,r=s+(h.options.width??1),e>=s&&e=zr)&&(Pi=r.options.x??0,Wr=Pi+(r.options.width??1),e>=Pi&&e=this._debounceThresholdMS)this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){let r=s-this._lastRefreshMs,n=this._debounceThresholdMS-r;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},n)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}},Kr=20,$r=!1,li=class extends I{constructor(e,t,i,s){super(),this._terminal=e,this._coreBrowserService=i,this._renderService=s,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="";let r=this._coreBrowserService.mainDocument;this._accessibilityContainer=r.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=r.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let n=0;nthis._handleBoundaryFocus(n,0),this._bottomBoundaryFocusListener=n=>this._handleBoundaryFocus(n,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Pl(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");$r?(this._accessibilityContainer.classList.add("debug"),this._rowContainer.classList.add("debug"),this._debugRootContainer=r.createElement("div"),this._debugRootContainer.classList.add("xterm"),this._debugRootContainer.appendChild(r.createTextNode("------start a11y------")),this._debugRootContainer.appendChild(this._accessibilityContainer),this._debugRootContainer.appendChild(r.createTextNode("------end a11y------")),this._terminal.element.insertAdjacentElement("afterend",this._debugRootContainer)):this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(n=>this._handleResize(n.rows))),this._register(this._terminal.onRender(n=>this._refreshRows(n.start,n.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(n=>this._handleChar(n))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` +`))),this._register(this._terminal.onA11yTab(n=>this._handleTab(n))),this._register(this._terminal.onKey(n=>this._handleKey(n.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(B(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(q(()=>{$r?this._debugRootContainer.remove():this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` +`&&(this._liveRegionLineCount++,this._liveRegionLineCount===Kr+1&&(this._liveRegion.textContent=ei.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){let n=i.lines.get(i.ydisp+r),o=[],h=n?.translateToString(!0,void 0,void 0,o)||"",l=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(h.length===0?(a.textContent="\xA0",this._rowColumns.set(a,[0,1])):(a.textContent=h,this._rowColumns.set(a,o)),a.setAttribute("aria-posinset",l),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent===ei.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){let i=e.target,s=this._rowElements[t===0?1:this._rowElements.length-2],r=i.getAttribute("aria-posinset"),n=t===0?"1":`${this._terminal.buffer.lines.length}`;if(r===n||e.relatedTarget!==s)return;let o,h;if(t===0?(o=i,h=this._rowElements.pop(),this._rowContainer.removeChild(h)):(o=this._rowElements.shift(),h=i,this._rowContainer.removeChild(o)),o.removeEventListener("focus",this._topBoundaryFocusListener),h.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){let l=this._createAccessibilityTreeNode();this._rowElements.unshift(l),this._rowContainer.insertAdjacentElement("afterbegin",l)}else{let l=this._createAccessibilityTreeNode();this._rowElements.push(l),this._rowContainer.appendChild(l)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error("anchorNode and/or focusNode are null");return}let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;let r=({node:h,offset:l})=>{let a=h instanceof Text?h.parentNode:h,d=parseInt(a?.getAttribute("aria-posinset"),10)-1;if(isNaN(d))return console.warn("row is invalid. Race condition?"),null;let c=this._rowColumns.get(a);if(!c)return console.warn("columns is null. Race condition?"),null;let u=l=this._terminal.cols&&(++d,u=0),{row:d,column:u}},n=r(t),o=r(i);if(!(!n||!o)){if(n.row>o.row||n.row===o.row&&n.column>=o.column)throw new Error("invalid range");this._terminal.select(n.column,n.row,(o.row-n.row)*this._terminal.cols-n.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;te;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Fi(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(B(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(B(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(B(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(B(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;let i=e.composedPath();for(let s=0;s{s?.forEach(r=>{r.link.dispose&&r.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(let[s,r]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(s)&&(i=this._checkLinkProviderResult(s,e,i)):r.provideLinks(e.y,n=>{if(this._isMouseOut)return;let o=n?.map(h=>({link:h}));this._activeProviderReplies?.set(s,o),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let i=new Set;for(let s=0;se?this._bufferService.cols:o.link.range.end.x;for(let a=h;a<=l;a++){if(i.has(a)){r.splice(n--,1);break}i.add(a)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;let s=this._activeProviderReplies.get(e),r=!1;for(let n=0;nthis._linkAtPosition(o.link,t));n&&(i=!0,this._handleNewLink(n))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let n=0;nthis._linkAtPosition(h.link,t));if(o){i=!0,this._handleNewLink(o);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink&&Tl(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Fi(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0?!0:e.link.decorations.underline,pointerCursor:e.link.decorations===void 0?!0:e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:i=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==i&&(this._currentLink.state.decorations.pointerCursor=i,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",i))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:i=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==i&&(this._currentLink.state.decorations.underline=i,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,i))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(i=>{if(!this._currentLink)return;let s=i.start===0?0:i.start+1+this._bufferService.buffer.ydisp,r=this._bufferService.buffer.ydisp+1+i.end;if(this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=r&&(this._clearCurrentLink(s,r),this._lastMouseEvent)){let n=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);n&&this._askForLink(n,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){let i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){let i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t,i){let s=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(s)return{x:s[0],y:s[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};ws=K([w(1,ys),w(2,Fe),w(3,ce),w(4,tn)],ws);function Tl(e,t){return e.text===t.text&&e.range.start.x===t.range.start.x&&e.range.start.y===t.range.start.y&&e.range.end.x===t.range.end.x&&e.range.end.y===t.range.end.y}var Al=class extends Bl{constructor(e={}){super(e),this._linkifier=this._register(new at),this.browser=Mn,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new at),this._onCursorMove=this._register(new b),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new b),this.onKey=this._onKey.event,this._onSelectionChange=this._register(new b),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new b),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new b),this.onBell=this._onBell.event,this._onFocus=this._register(new b),this._onBlur=this._register(new b),this._onA11yCharEmitter=this._register(new b),this._onA11yTabEmitter=this._register(new b),this._onWillOpen=this._register(new b),this._onDimensionsChange=this._register(new b),this.onDimensionsChange=this._onDimensionsChange.event,this._setup(),this._decorationService=this._instantiationService.createInstance(Rl),this._instantiationService.setService(At,this._decorationService),this._keyboardService=this._instantiationService.createInstance(us),this._instantiationService.setService(vo,this._keyboardService),this._linkProviderService=this._instantiationService.createInstance(bh),this._instantiationService.setService(tn,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(Ni)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(t=>this.refresh(t?.start??0,t?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(t=>this._reportWindowsOptions(t))),this._register(this._inputHandler.onColor(t=>this._handleColorEvent(t))),this._register(_e.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(_e.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(_e.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(_e.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(t=>this._afterResize(t.cols,t.rows))),this._register(q(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}get dimensions(){if(!this._renderService)return;let e=this._renderService.dimensions;return{css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}}_handleColorEvent(e){if(this._themeService)for(let t of e){let i,s="";switch(t.index){case 256:i="foreground",s="10";break;case 257:i="background",s="11";break;case 258:i="cursor",s="12";break;default:i="ansi",s="4;"+t.index}switch(t.type){case 0:let r=N.toColorRGB(i==="ansi"?this._themeService.colors.ansi[t.index]:this._themeService.colors[i]);this.coreService.triggerDataEvent(`\x1B]${s};${wl(r)}\x1B\\`);break;case 1:if(i==="ansi")this._themeService.modifyColors(n=>n.ansi[t.index]=V.toColor(...t.color));else{let n=i;this._themeService.modifyColors(o=>o[n]=V.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_reportColorScheme(){if(!this._themeService)return;let e=oe.relativeLuminance(this._themeService.colors.background.rgba>>8),t=oe.relativeLuminance(this._themeService.colors.foreground.rgba>>8),i=e{this.hasSelection()&&oo(t,this._selectionService)}));let e=t=>ao(t,this.textarea,this.coreService,this.optionsService);this._register(B(this.textarea,"paste",e)),this._register(B(this.element,"paste",e)),Ln?this._register(B(this.element,"mousedown",t=>{t.button===2&&Ws(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(B(this.element,"contextmenu",t=>{Ws(t,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),As&&this._register(B(this.element,"auxclick",t=>{t.button===1&&Yr(t,this.textarea,this.screenElement)}))}_bindKeys(){this._register(B(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(B(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(B(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(B(this.textarea,"compositionstart",()=>this._compositionHelper.compositionstart())),this._register(B(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(B(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(B(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",r=>this.element.classList.toggle("allow-transparency",r))),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(B(this.screenElement,"mousemove",r=>this.updateCursorStyle(r))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",Oi.get()),An||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(Sh,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<"u"?window.document:null)),this._instantiationService.setService(Ne,this._coreBrowserService),this._register(B(this.textarea,"focus",r=>this._handleTextAreaFocus(r))),this._register(B(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(as,this._document,this._helperContainer),this._instantiationService.setService(ci,this._charSizeService),this._themeService=this._instantiationService.createInstance(ds),this._instantiationService.setService(lt,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(ai),this._instantiationService.setService(en,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(ls,this.rows,this.screenElement)),this._instantiationService.setService(Fe,this._renderService),this._register(this._renderService.onRenderedViewportChange(r=>this._onRender.fire(r))),this._register(this._renderService.onDimensionsChange(r=>this._onDimensionsChange.fire({css:{canvas:{...r.css.canvas},cell:{...r.css.cell}},device:{canvas:{...r.device.canvas},cell:{...r.device.cell},char:{...r.device.char}}}))),this.onResize(r=>this._renderService.resize(r.cols,r.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(rs,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(hs),this._instantiationService.setService(ys,this._mouseService);let s=this._linkifier.value=this._register(this._instantiationService.createInstance(ws,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(is,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(r=>{super.scrollLines(r,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(cs,this.element,this.screenElement,s)),this._instantiationService.setService(go,this._selectionService),this._register(this._selectionService.onRequestScrollLines(r=>this.scrollLines(r.amount,r.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(r=>this._renderService.handleSelectionChanged(r.start,r.end,r.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(r=>{this.textarea.value=r,this.textarea.focus(),this.textarea.select()})),this._register(_e.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(ss,this.screenElement)),this._register(B(this.element,"mousedown",r=>this._selectionService.handleMouseDown(r))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(li,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",r=>this._handleScreenReaderModeOptionChange(r))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(oi,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRuler",r=>{!this._overviewRulerRenderer&&r&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(oi,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(os,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,t=this.element;function i(n){let o=e._mouseService?.getMouseReportCoords(n,e.screenElement);if(!o)return!1;let h,l;switch(n.overrideType||n.type){case"mousemove":l=32,n.buttons===void 0?(h=3,n.button!==void 0&&(h=n.button<3?n.button:3)):h=n.buttons&1?0:n.buttons&4?1:n.buttons&2?2:3;break;case"mouseup":l=0,h=n.button<3?n.button:3;break;case"mousedown":l=1,h=n.button<3?n.button:3;break;case"wheel":if(e._customWheelEventHandler&&e._customWheelEventHandler(n)===!1)return!1;let a=n.deltaY;if(a===0||e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;l=a<0?0:1,h=4;break;default:return!1}return l===void 0||h===void 0||h>4?!1:e.coreMouseService.triggerMouseEvent({col:o.col,row:o.row,x:o.x,y:o.y,button:h,action:l,ctrl:n.ctrlKey,alt:n.altKey,shift:n.shiftKey})}let s={mouseup:null,wheel:null,mousedrag:null,mousemove:null},r={mouseup:n=>{i(n),n.buttons||(this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag))},wheel:n=>(i(n),n.preventDefault(),n.stopPropagation(),!1),mousedrag:n=>{n.buttons&&i(n)},mousemove:n=>{n.buttons||i(n)}};this._register(this.coreMouseService.onProtocolChange(n=>{n?(this.optionsService.rawOptions.logLevel==="debug"&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(n)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),n&8?s.mousemove||(t.addEventListener("mousemove",r.mousemove),s.mousemove=r.mousemove):(t.removeEventListener("mousemove",s.mousemove),s.mousemove=null),n&16?s.wheel||(t.addEventListener("wheel",r.wheel,{passive:!1}),s.wheel=r.wheel):(t.removeEventListener("wheel",s.wheel),s.wheel=null),n&2?s.mouseup??(s.mouseup=r.mouseup):(this._document.removeEventListener("mouseup",s.mouseup),s.mouseup=null),n&4?s.mousedrag??(s.mousedrag=r.mousedrag):(this._document.removeEventListener("mousemove",s.mousedrag),s.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(q(()=>{s.mouseup&&this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag)})),this._register(B(t,"mousedown",n=>{n.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(n))&&(i(n),s.mouseup&&this._document.addEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.addEventListener("mousemove",s.mousedrag))})),this._register(B(t,"wheel",n=>{if(!s.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(n)===!1)return!1;if(!this.buffer.hasScrollback){if(n.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(n,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return n.preventDefault(),n.stopPropagation(),!1;let o="\x1B"+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(n.deltaY<0?"A":"B");return this.coreService.triggerDataEvent(o,!0),n.preventDefault(),n.stopPropagation(),!1}}},{passive:!1}))}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){Vr(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),i.type===3||i.type===2){let r=this.rows-1;return this.scrollLines(i.type===2?-r:r),e.preventDefault(),e.stopPropagation(),!1}if(i.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)||!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;(i.key===""||i.key==="\r")&&(this.textarea.value="");let s=this._keyboardService.useWin32InputMode&&Ai(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){let i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return t.type==="keypress"?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return;Ai(e)||this.focus();let t=this._keyboardService.evaluateKeyUp(e);if(t?.key){let i=this._keyboardService.useWin32InputMode&&Ai(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType==="insertText"&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(!(this.buffer.ybase===0&&this.buffer.y===0)){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){let i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new ke)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}},Ur=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){let t=this._buffer.lines.get(e);if(t)return new Il(t)}getNullCell(){return new ke}},Nl=class extends I{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new b),this.onBufferChange=this._onBufferChange.event,this._normal=new Ur(this._core.buffers.normal,"normal"),this._alternate=new Ur(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}},Fl=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,i=>t(i.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(i,s)=>t(i,s.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}},Hl=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}},Wl=["cols","rows"],De=0,Gn=class extends I{constructor(e){super(),this._core=this._register(new Al(e)),this._addonManager=this._register(new Ol),this._publicOptions={...this._core.options};let t=s=>this._core.options[s],i=(s,r)=>{this._checkReadonlyOptions(s),this._core.options[s]=r};for(let s in this._core.options){let r={get:t.bind(this,s),set:i.bind(this,s)};Object.defineProperty(this._publicOptions,s,r)}}_checkReadonlyOptions(e){if(Wl.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get parser(){return this._parser??(this._parser=new Fl(this._core))}get unicode(){return this._checkProposedApi(),new Hl(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??(this._buffer=this._register(new Nl(this._core)))}get markers(){return this._core.markers}get modes(){let e=this._core.coreService.decPrivateModes,t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any";break}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(let t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write(`\r +`,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return Oi.get()},set promptLabel(e){Oi.set(e)},get tooMuchOutput(){return ei.get()},set tooMuchOutput(e){ei.set(e)}}}_verifyIntegers(...e){for(De of e)if(De===1/0||isNaN(De)||De%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(De of e)if(De&&(De===1/0||isNaN(De)||De%1!==0||De<0))throw new Error("This API only accepts positive integers")}};/** * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved. * @license MIT * @@ -55,4 +55,4 @@ ${d.join(` * Fabrice Bellard's javascript vt100 for jslinux: * http://bellard.org/jslinux/ * Copyright (c) 2011 Fabrice Bellard - */var Fl=2,Hl=1;function Wl(e){return e?.ownerDocument?.defaultView?e.ownerDocument.defaultView:window}function Xn(e){return Wl(e).getComputedStyle(e,null)}var Gn=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows)||(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&this._terminal.resize(e.cols,e.rows)}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let e=this._terminal.dimensions;if(!e||e.css.cell.width===0||e.css.cell.height===0)return;let t=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,i=Xn(this._terminal.element.parentElement),s=parseInt(i.getPropertyValue("height")),r=Math.max(0,parseInt(i.getPropertyValue("width"))),n=Xn(this._terminal.element),o={top:parseInt(n.getPropertyValue("padding-top")),bottom:parseInt(n.getPropertyValue("padding-bottom")),right:parseInt(n.getPropertyValue("padding-right")),left:parseInt(n.getPropertyValue("padding-left"))},h=o.top+o.bottom,l=o.right+o.left,a=s-h,d=r-l-t;return{cols:Math.max(Fl,Math.floor(d/e.css.cell.width)),rows:Math.max(Hl,Math.floor(a/e.css.cell.height))}}};var Fe=new jn({cursorBlink:!0,scrollback:5e3,convertEol:!1,fontSize:14}),Os=new Gn;Fe.loadAddon(Os);Fe.open(document.getElementById("terminal"));var Jn="",ui=-1,As=[],Ts=!1;function Zn(){if(Ts||!As.length)return;let e=As.shift();Ts=!0,e.reset&&Fe.reset(),e.theme&&(Fe.options.theme=e.theme),Fe.write(e.data,()=>{Ts=!1,Zn()})}function ct(e){window.OpenBitFunTerminalHost?window.OpenBitFunTerminalHost.postMessage(JSON.stringify(e)):window.webkit?.messageHandlers?.openbitfunTerminal?.postMessage(e)}Fe.onData(e=>ct({type:"input",data:e}));Fe.onResize(({cols:e,rows:t})=>ct({type:"resize",cols:e,rows:t}));new ResizeObserver(()=>Os.fit()).observe(document.getElementById("terminal"));window.OpenBitFunTerminal={connect(){Os.fit(),ct({type:"ready"}),ct({type:"resize",cols:Fe.cols,rows:Fe.rows})},accept(e){if(!(typeof e.epoch!="string"||!Number.isSafeInteger(e.revision)||typeof e.data!="string")){if(e.epoch!==Jn){if(!e.reset){ct({type:"resync"});return}Jn=e.epoch,ui=-1}if(!(e.revision<=ui)){if(!e.reset&&e.revision!==ui+1){ct({type:"resync"});return}As.push(e),ui=e.revision,Zn()}}}};window.OpenBitFunTerminal.connect();})(); + */var zl=2,Kl=1;function $l(e){return e?.ownerDocument?.defaultView?e.ownerDocument.defaultView:window}function Jn(e){return $l(e).getComputedStyle(e,null)}var Zn=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows)||(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&this._terminal.resize(e.cols,e.rows)}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let e=this._terminal.dimensions;if(!e||e.css.cell.width===0||e.css.cell.height===0)return;let t=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,i=Jn(this._terminal.element.parentElement),s=parseInt(i.getPropertyValue("height")),r=Math.max(0,parseInt(i.getPropertyValue("width"))),n=Jn(this._terminal.element),o={top:parseInt(n.getPropertyValue("padding-top")),bottom:parseInt(n.getPropertyValue("padding-bottom")),right:parseInt(n.getPropertyValue("padding-right")),left:parseInt(n.getPropertyValue("padding-left"))},h=o.top+o.bottom,l=o.right+o.left,a=s-h,d=r-l-t;return{cols:Math.max(zl,Math.floor(d/e.css.cell.width)),rows:Math.max(Kl,Math.floor(a/e.css.cell.height))}}};var Le=new Gn({cursorBlink:!0,scrollback:5e3,convertEol:!1,fontSize:14}),Ns=new Zn;Le.loadAddon(Ns);Le.open(document.getElementById("terminal"));var It=document.getElementById("terminal"),He;It.addEventListener("touchstart",e=>{let t=e.touches[0];He=e.touches.length===1?{id:t.identifier,x:t.clientX,y:t.clientY,started:e.timeStamp}:void 0},{passive:!0});It.addEventListener("touchmove",e=>{let t=Array.from(e.touches).find(i=>i.identifier===He?.id);(!t||!He||Math.hypot(t.clientX-He.x,t.clientY-He.y)>10)&&(He=void 0)},{passive:!0});It.addEventListener("touchcancel",()=>{He=void 0},{passive:!0});It.addEventListener("touchend",e=>{let t=He;He=void 0,t&&e.touches.length===0&&e.timeStamp-t.started<500&&Array.from(e.changedTouches).some(i=>i.identifier===t.id&&Math.hypot(i.clientX-t.x,i.clientY-t.y)<=10)&&Le.focus()},{passive:!0});function eo(e){Le.options.theme=e,It.style.backgroundColor=e.background}var Qn="",fi=-1,Is=[],Os=!1;function to(){if(Os||!Is.length)return;let e=Is.shift();Os=!0,e.reset&&Le.reset(),e.theme&&eo(e.theme),Le.write(e.data,()=>{Os=!1,to()})}function dt(e){window.OpenBitFunTerminalHost?window.OpenBitFunTerminalHost.postMessage(JSON.stringify(e)):window.webkit?.messageHandlers?.openbitfunTerminal?.postMessage(e)}Le.onData(e=>dt({type:"input",data:e}));Le.onResize(({cols:e,rows:t})=>dt({type:"resize",cols:e,rows:t}));new ResizeObserver(()=>Ns.fit()).observe(document.getElementById("terminal"));window.OpenBitFunTerminal={setTheme:eo,connect(){Ns.fit(),dt({type:"ready"}),dt({type:"resize",cols:Le.cols,rows:Le.rows})},accept(e){if(!(typeof e.epoch!="string"||!Number.isSafeInteger(e.revision)||typeof e.data!="string")){if(e.epoch!==Qn){if(!e.reset){dt({type:"resync"});return}Qn=e.epoch,fi=-1}if(!(e.revision<=fi)){if(!e.reset&&e.revision!==fi+1){dt({type:"resync"});return}Is.push(e),fi=e.revision,to()}}}};window.OpenBitFunTerminal.connect();})(); diff --git a/src/shared/terminal/webview/index.ts b/src/shared/terminal/webview/index.ts index eda0374ce2..5a57d41365 100644 --- a/src/shared/terminal/webview/index.ts +++ b/src/shared/terminal/webview/index.ts @@ -3,20 +3,48 @@ import { FitAddon } from '@xterm/addon-fit'; import '@xterm/xterm/css/xterm.css'; type Event = {type:'ready'|'resync'} | {type:'input';data:string} | {type:'resize';cols:number;rows:number}; -type Frame = {epoch:string;revision:number;reset:boolean;data:string;theme?:{background:string;foreground:string;cursor?:string}}; +type Theme = {background:string;foreground:string;cursor?:string}; +type Frame = {epoch:string;revision:number;reset:boolean;data:string;theme?:Theme}; declare global { interface Window { OpenBitFunTerminalHost?:{postMessage:(message:string)=>void}; webkit?:{messageHandlers?:{openbitfunTerminal?:{postMessage:(event:Event)=>void}}}; - OpenBitFunTerminal:{connect:()=>void;accept:(frame:Frame)=>void}; + OpenBitFunTerminal:{connect:()=>void;setTheme:(theme:Theme)=>void;accept:(frame:Frame)=>void}; } } const terminal = new Terminal({cursorBlink:true,scrollback:5000,convertEol:false,fontSize:14}); const fit = new FitAddon();terminal.loadAddon(fit);terminal.open(document.getElementById('terminal')!); +// xterm focuses on mousedown, which mobile WebViews need not synthesize for +// touch. Focus synchronously on a tap so the OS can open its keyboard, while +// leaving scrolling, selection and multi-touch gestures to xterm/the WebView. +const surface = document.getElementById('terminal')!; +let tap: {id:number;x:number;y:number;started:number} | undefined; +surface.addEventListener('touchstart', event => { + const touch = event.touches[0]; + tap = event.touches.length === 1 + ? {id:touch.identifier,x:touch.clientX,y:touch.clientY,started:event.timeStamp} + : undefined; +}, {passive:true}); +surface.addEventListener('touchmove', event => { + const touch = Array.from(event.touches).find(touch => touch.identifier === tap?.id); + if (!touch || !tap || Math.hypot(touch.clientX-tap.x,touch.clientY-tap.y) > 10) tap = undefined; +}, {passive:true}); +surface.addEventListener('touchcancel', () => { tap = undefined; }, {passive:true}); +surface.addEventListener('touchend', event => { + const ended = tap; + tap = undefined; + if (ended && event.touches.length === 0 && event.timeStamp-ended.started < 500 && + Array.from(event.changedTouches).some(touch => touch.identifier === ended.id && + Math.hypot(touch.clientX-ended.x,touch.clientY-ended.y) <= 10)) terminal.focus(); +}, {passive:true}); +function applyTheme(theme:Theme) { + terminal.options.theme=theme; + surface.style.backgroundColor=theme.background; +} let epoch='';let revision=-1; const pending: Frame[]=[];let writing=false; function drain(){ if(writing||!pending.length)return; const frame=pending.shift()!;writing=true; - if(frame.reset)terminal.reset();if(frame.theme)terminal.options.theme=frame.theme; + if(frame.reset)terminal.reset();if(frame.theme)applyTheme(frame.theme); terminal.write(frame.data,()=>{writing=false;drain();}); } function send(event:Event) { @@ -27,6 +55,7 @@ terminal.onData(data=>send({type:'input',data})); terminal.onResize(({cols,rows})=>send({type:'resize',cols,rows})); new ResizeObserver(()=>fit.fit()).observe(document.getElementById('terminal')!); window.OpenBitFunTerminal={ + setTheme:applyTheme, connect(){fit.fit();send({type:'ready'});send({type:'resize',cols:terminal.cols,rows:terminal.rows});}, accept(frame){ if(typeof frame.epoch!=='string'||!Number.isSafeInteger(frame.revision)||typeof frame.data!=='string')return;