diff --git a/Glint.xcodeproj/project.pbxproj b/Glint.xcodeproj/project.pbxproj index 7d98af5..8e8eace 100644 --- a/Glint.xcodeproj/project.pbxproj +++ b/Glint.xcodeproj/project.pbxproj @@ -7,9 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - D7A100000000000000000002 /* PerformanceRegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A100000000000000000001 /* PerformanceRegressionTests.swift */; }; 04E75EAD5165E8D96C1C5517 /* MascotAssetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349714D49AE52D84995314FD /* MascotAssetTests.swift */; }; 07817DC6A98F8ED2E236CFA1 /* CodexHome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E3B385162995D0F1F5B72D /* CodexHome.swift */; }; + 09AAB5DF2BA70FCAADAB4C2C /* SymbolsNerdFontMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 911638005A9881330F4F855F /* SymbolsNerdFontMono-Regular.ttf */; }; 0D3661ECB91851F77B1E48CE /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = B957E30052DD894C5B6F1063 /* terminfo */; }; 0FB9792E290FC2A8B527B2D0 /* GitRepositoryWatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C888E59D1091BFC22A9EA4 /* GitRepositoryWatcherTests.swift */; }; 0FEECB27EB1ACE11E29ECCEF /* CodexHookInstallerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26141150D2DB4C0589558ECC /* CodexHookInstallerTests.swift */; }; @@ -25,29 +25,39 @@ 2E52C3AC5AD998CD3604D6BE /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DADCF27E710CC77846E961 /* GhosttyKit.xcframework */; }; 3194A1545E36E400BC2C33E6 /* PaneSessionIdMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797C4E86D052EF8A68DD9B39 /* PaneSessionIdMigrationTests.swift */; }; 34997DD4A4F9B64A754FE77F /* DevinHookInstallerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F58E2127BAE2C9F5155CF07 /* DevinHookInstallerTests.swift */; }; - 3521283B78CD4A34A8725243 /* OmpHookInstallerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1228ACED02A84EE4A7AE5C78 /* OmpHookInstallerTests.swift */; }; - DEA00336B2F4043FD6D527F2 /* GrokHookInstallerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFAAA8D93863E47CBF04F37C /* GrokHookInstallerTests.swift */; }; 370FA310A4B7CC8489BF51DE /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712159D2C27AD5B865820384 /* WhatsNewView.swift */; }; 3932A4CFEFF6FB81AE283645 /* AgentHookInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265A192AC9BD06DB09346E90 /* AgentHookInstaller.swift */; }; + 3B439B6978244B06B9695824 /* nerd-fonts-LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2B1F1216422BE2A4234ABA44 /* nerd-fonts-LICENSE.txt */; }; 3CB9F41E3B1CB4A8790D9159 /* GhosttyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC6FFB5185C403F6F44A1687 /* GhosttyManager.swift */; }; 3EFBC4D177ED22F347A7AFB0 /* GitRepositoryWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FB2646BF84D9DCBB89FC93 /* GitRepositoryWatcher.swift */; }; 419DDD815048044125F5FE91 /* ReviewWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E982534C8CF6270BCF39106 /* ReviewWindow.swift */; }; + 432148F665B867944BD134BF /* web-remote.js in Resources */ = {isa = PBXBuildFile; fileRef = 07C2CEDE2C717743C29E6026 /* web-remote.js */; }; + 44C33E4DC10425D7555AC4E2 /* WebRemoteServerIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C96BF92C4618FF97D68919 /* WebRemoteServerIntegrationTests.swift */; }; 456D76955277C3299291BF60 /* SyntaxHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8037A4E0001A21E3224DD798 /* SyntaxHighlighter.swift */; }; 47BB527385B84454ABF8FC40 /* themes.json in Resources */ = {isa = PBXBuildFile; fileRef = 6860F8F626EFDB9A289FC727 /* themes.json */; }; 4971664CE7FBF0629C8DD709 /* ReleaseNotes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407B3A9D15F4B9E613DA1CF8 /* ReleaseNotes.swift */; }; 49DF8EE59313B7CFE29DA303 /* PaneTreeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E646AABA8ADCE8A2A5C6F55 /* PaneTreeView.swift */; }; + 4EC47B8DA99A679D88935CB8 /* PerformanceRegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43EEEA275BAC1522032064C /* PerformanceRegressionTests.swift */; }; 4ED4428EEC2AFB777092FAEF /* ShellRcBlockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B91F000C01FEA1C7E61F5A /* ShellRcBlockTests.swift */; }; 5157C5439862F21202BCC44D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E67CF5D8552AE1F51386161 /* AppDelegate.swift */; }; + 57D99FDEAF8596DCDBC55ABE /* GrokHookInstallerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDAAD70DC4C2FC80F6DEA51 /* GrokHookInstallerTests.swift */; }; 6290F26DFE2D0D5E1FEB9FA9 /* GitRefreshCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB695363FAF3A996EF0C9FF /* GitRefreshCoordinator.swift */; }; + 632C9446752A79B9C95A5D0D /* addon-fit-LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 677808A57BE53607DA391FC8 /* addon-fit-LICENSE.txt */; }; 692D1A7D490E07D796535EAA /* UpdaterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4408F9761E584DE2AB6630E2 /* UpdaterController.swift */; }; + 6B6B183329BCCDE5474D88AF /* web-remote.css in Resources */ = {isa = PBXBuildFile; fileRef = ABF0871355239B5D7C80B719 /* web-remote.css */; }; + 6BE78104E4CC97417ED400B4 /* xterm.mjs in Resources */ = {isa = PBXBuildFile; fileRef = 36D0A3B6CB143C412B8EDA7B /* xterm.mjs */; }; 6D74FE6545631EB8193C82E1 /* PaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FC2FBC9E8729A5ECBF857C /* PaneView.swift */; }; + 6EE719F5F1D8C81751C74445 /* xterm.css in Resources */ = {isa = PBXBuildFile; fileRef = 49C5AC93BE947131B5611EF8 /* xterm.css */; }; 7072B8575DB70B2367A7EE1D /* ReviewScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F8C1899F93B387843907FF /* ReviewScopeTests.swift */; }; + 79467AAA8C1E5D47194A8173 /* xterm-LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = BD31B21D1D50D85D5D5E366A /* xterm-LICENSE.txt */; }; 7D07A40DF6411A613624827A /* PaneSurfaceRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16137B405E48DBE262A15B6D /* PaneSurfaceRepresentable.swift */; }; + 7E6DBB055150A4D140351EB6 /* symbols-nerd-font-LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5627A3DA0ADBDC6194E2D7A7 /* symbols-nerd-font-LICENSE.txt */; }; 7FC12666816053BEB64079CB /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E267AE14712D4D6633B4F /* Theme.swift */; }; 80C9187503230DFF69389EFE /* InlineSuggestionInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BBD4C70C8DC534304294C9 /* InlineSuggestionInstaller.swift */; }; 81532B36FEC3039B8EAD9CB1 /* AgentKindResolutionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEF00CBEF45A9D60423A207 /* AgentKindResolutionTests.swift */; }; 930665E39CE84564CBF9D300 /* GlintTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 150EEF32284061D527E51866 /* GlintTheme.swift */; }; 942ED7C1C1A2E823BA52F67A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3023490E670A10EC38870B12 /* Assets.xcassets */; }; + 9A5EC6D5F8F65EE762FCD917 /* addon-fit.mjs in Resources */ = {isa = PBXBuildFile; fileRef = 5DB06D8C68CA7B9C7E1E83E7 /* addon-fit.mjs */; }; 9F605AB2DC185A94ED28E6D4 /* AgentChoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EB9A1E80F3CC4BCA468774 /* AgentChoice.swift */; }; A019A2AD685F1407D7E2A7E1 /* AgentHookRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0082D535969C315B8D75D7 /* AgentHookRoutingTests.swift */; }; A91EFEF0FEA392A0745724DE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 89B8883C4456681F07B6D662 /* AppIcon.icon */; }; @@ -55,6 +65,7 @@ AB89FA6EF5785EC4026F4402 /* VisualEffectBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0656F7C1D48AE0429C1888B /* VisualEffectBackground.swift */; }; ABFF3BCAC4AE8CCCAFF89CA0 /* NewWorkspaceSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D29C0D2447094B07854C193 /* NewWorkspaceSheet.swift */; }; B1DE20B081ACC4536DBD2089 /* GitStatusPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54663E213FE879B96C0D80E7 /* GitStatusPopover.swift */; }; + B3EEBFD73C01D337FA3369BD /* WebRemoteProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEEB37F39E654EF9574D8098 /* WebRemoteProtocolTests.swift */; }; B70CAFEBB2416E25B0FFED97 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = C48F9D3C512A0ABE8369D0F4 /* Sparkle */; }; BFF9A1736CA029241BB0622D /* SSHGitRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8EDEAA2D0EAD42ED6F1915 /* SSHGitRunnerTests.swift */; }; C0667FA434EF07D98DED44D1 /* AEBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D5B5D0462A1225A56E6EE85 /* AEBridge.m */; }; @@ -70,9 +81,13 @@ D0C5A885F5B3FE6BD5AFC54A /* CloseOnCmdW.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E574E2A2735328331FC3C78 /* CloseOnCmdW.swift */; }; D1BDE1E5DE8D4FE1F96822D1 /* WorkspaceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A8933F67B4B6EEA3FD4E603 /* WorkspaceStore.swift */; }; D3D6CF13B67D30E9DA557F83 /* UsageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1F24DAA7220AD4EE961E2 /* UsageStore.swift */; }; + D6538EB0E59B35FB96AD80E2 /* web-remote-index.html in Resources */ = {isa = PBXBuildFile; fileRef = B7CBC5C072019BC8A7C08327 /* web-remote-index.html */; }; + DFDF7E690BD53E885484EA00 /* OmpHookInstallerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE16D678B1BC67A99EBA74F /* OmpHookInstallerTests.swift */; }; E464D0233508CD31C1235054 /* AgentBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F2CA2243872C8111048EF2 /* AgentBridge.swift */; }; E80809A987A07731FF35D01D /* PersistenceRecoveryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DE2D7740BD03498A936B62 /* PersistenceRecoveryTests.swift */; }; + EAC3EE05466931E8AAF620D2 /* WebRemoteProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943254F613D20C4F0941EFA6 /* WebRemoteProtocol.swift */; }; ED46CD7A6E09551A775AD3B8 /* PaneAgentKindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC04C19D745AA867D961FD /* PaneAgentKindTests.swift */; }; + EE6D05AB1D935F502865ABDA /* crypto-LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2C85E16653B066382F22FEDD /* crypto-LICENSE.txt */; }; EF3ACBA990526130228FAD0B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D3B64992BBD5A190DFE36D2 /* SettingsView.swift */; }; EF937781512647E835836F83 /* ControlBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21868C7211897C87A6CAD7C3 /* ControlBridge.swift */; }; F584D3237E44E7444086E011 /* ShellRcBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9550DD194D906A2B5168DBFD /* ShellRcBlock.swift */; }; @@ -80,9 +95,11 @@ F6AFF715FCE5357CEB07683D /* SettingsSafety.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB20921E3CAC1006A8FBD3AE /* SettingsSafety.swift */; }; F7ABF70CD455143BF89B89B3 /* SafeJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E2B9ADF809766CE4278047 /* SafeJSON.swift */; }; FAB0828152636BBBBC5989CD /* SyntaxHighlighterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAD36B4BDB22AB47353C569 /* SyntaxHighlighterTests.swift */; }; + FB4C8529271BF52873745556 /* crypto.mjs in Resources */ = {isa = PBXBuildFile; fileRef = F6DDF70810465204EB990667 /* crypto.mjs */; }; FCB67525DF16A4DCF9B2AF7A /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 376568363043C189904E4E0A /* Localizable.xcstrings */; }; FE527C4220DD46528255F14E /* ModalOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11B3335E47DDAF77A883064 /* ModalOverlay.swift */; }; FEBC3158CB471E9ABDF931BB /* CodexUsageReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FBF22AEC81DBE92987A8CB4 /* CodexUsageReaderTests.swift */; }; + FF24B6DE3A9ED42C8C68D16C /* WebRemoteServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3AFEC99ADB408EF21B6530E /* WebRemoteServer.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -96,17 +113,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - D7A100000000000000000001 /* PerformanceRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceRegressionTests.swift; sourceTree = ""; }; 050E267AE14712D4D6633B4F /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 05F2CA2243872C8111048EF2 /* AgentBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentBridge.swift; sourceTree = ""; }; + 07C2CEDE2C717743C29E6026 /* web-remote.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "web-remote.js"; sourceTree = ""; }; 08FC2FBC9E8729A5ECBF857C /* PaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneView.swift; sourceTree = ""; }; 0C5FD3EEA1ECD40F78394862 /* AgentLaunchChooser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentLaunchChooser.swift; sourceTree = ""; }; 0D29C0D2447094B07854C193 /* NewWorkspaceSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWorkspaceSheet.swift; sourceTree = ""; }; 0D5B5D0462A1225A56E6EE85 /* AEBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AEBridge.m; sourceTree = ""; }; 0D751E30DFF2EFA6F705A374 /* GhosttyTickSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyTickSchedulerTests.swift; sourceTree = ""; }; 0F58E2127BAE2C9F5155CF07 /* DevinHookInstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevinHookInstallerTests.swift; sourceTree = ""; }; - 1228ACED02A84EE4A7AE5C78 /* OmpHookInstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmpHookInstallerTests.swift; sourceTree = ""; }; - FFAAA8D93863E47CBF04F37C /* GrokHookInstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrokHookInstallerTests.swift; sourceTree = ""; }; 0FBF22AEC81DBE92987A8CB4 /* CodexUsageReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodexUsageReaderTests.swift; sourceTree = ""; }; 150EEF32284061D527E51866 /* GlintTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlintTheme.swift; sourceTree = ""; }; 16137B405E48DBE262A15B6D /* PaneSurfaceRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneSurfaceRepresentable.swift; sourceTree = ""; }; @@ -118,9 +133,12 @@ 25DADCEBA9C483C729B69388 /* Glint.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Glint.entitlements; sourceTree = ""; }; 26141150D2DB4C0589558ECC /* CodexHookInstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodexHookInstallerTests.swift; sourceTree = ""; }; 265A192AC9BD06DB09346E90 /* AgentHookInstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentHookInstaller.swift; sourceTree = ""; }; + 2B1F1216422BE2A4234ABA44 /* nerd-fonts-LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "nerd-fonts-LICENSE.txt"; sourceTree = ""; }; + 2C85E16653B066382F22FEDD /* crypto-LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "crypto-LICENSE.txt"; sourceTree = ""; }; 2CFFB91CE5C44976A6961F1F /* ReviewDiffParsingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewDiffParsingTests.swift; sourceTree = ""; }; 3023490E670A10EC38870B12 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 349714D49AE52D84995314FD /* MascotAssetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MascotAssetTests.swift; sourceTree = ""; }; + 36D0A3B6CB143C412B8EDA7B /* xterm.mjs */ = {isa = PBXFileReference; path = xterm.mjs; sourceTree = ""; }; 376568363043C189904E4E0A /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 38E3B385162995D0F1F5B72D /* CodexHome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodexHome.swift; sourceTree = ""; }; 39F54835E4A44EC88236EFBE /* GitService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitService.swift; sourceTree = ""; }; @@ -129,6 +147,8 @@ 407B3A9D15F4B9E613DA1CF8 /* ReleaseNotes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseNotes.swift; sourceTree = ""; }; 41DDB9C7B5A277B7924270A3 /* PaneAgentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneAgentState.swift; sourceTree = ""; }; 4408F9761E584DE2AB6630E2 /* UpdaterController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdaterController.swift; sourceTree = ""; }; + 45C96BF92C4618FF97D68919 /* WebRemoteServerIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRemoteServerIntegrationTests.swift; sourceTree = ""; }; + 49C5AC93BE947131B5611EF8 /* xterm.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = xterm.css; sourceTree = ""; }; 49C888E59D1091BFC22A9EA4 /* GitRepositoryWatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitRepositoryWatcherTests.swift; sourceTree = ""; }; 4A0B24E791FF6CEB30A9DDD5 /* RemoteTitleParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteTitleParserTests.swift; sourceTree = ""; }; 4B8EDEAA2D0EAD42ED6F1915 /* SSHGitRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHGitRunnerTests.swift; sourceTree = ""; }; @@ -136,8 +156,12 @@ 4EEF00CBEF45A9D60423A207 /* AgentKindResolutionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentKindResolutionTests.swift; sourceTree = ""; }; 51DE2D7740BD03498A936B62 /* PersistenceRecoveryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceRecoveryTests.swift; sourceTree = ""; }; 54663E213FE879B96C0D80E7 /* GitStatusPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitStatusPopover.swift; sourceTree = ""; }; + 5627A3DA0ADBDC6194E2D7A7 /* symbols-nerd-font-LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "symbols-nerd-font-LICENSE.txt"; sourceTree = ""; }; 5A8933F67B4B6EEA3FD4E603 /* WorkspaceStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceStore.swift; sourceTree = ""; }; + 5BDAAD70DC4C2FC80F6DEA51 /* GrokHookInstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrokHookInstallerTests.swift; sourceTree = ""; }; + 5DB06D8C68CA7B9C7E1E83E7 /* addon-fit.mjs */ = {isa = PBXFileReference; path = "addon-fit.mjs"; sourceTree = ""; }; 65BE76E8E627A5347D565968 /* GitRefreshCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitRefreshCoordinatorTests.swift; sourceTree = ""; }; + 677808A57BE53607DA391FC8 /* addon-fit-LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "addon-fit-LICENSE.txt"; sourceTree = ""; }; 6860F8F626EFDB9A289FC727 /* themes.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = themes.json; sourceTree = ""; }; 6AB338782EF6F22B7B381DA8 /* CodexHomeStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodexHomeStoreTests.swift; sourceTree = ""; }; 6D3B64992BBD5A190DFE36D2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; @@ -151,13 +175,19 @@ 83E4DF4C5180D50C381B4976 /* WorkspaceIconKindTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceIconKindTests.swift; sourceTree = ""; }; 89B8883C4456681F07B6D662 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = wrapper.icon; path = AppIcon.icon; sourceTree = ""; }; 8EDE0D7C434B8E9C76C4A297 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; + 911638005A9881330F4F855F /* SymbolsNerdFontMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SymbolsNerdFontMono-Regular.ttf"; sourceTree = ""; }; + 943254F613D20C4F0941EFA6 /* WebRemoteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRemoteProtocol.swift; sourceTree = ""; }; 9550DD194D906A2B5168DBFD /* ShellRcBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellRcBlock.swift; sourceTree = ""; }; 98BBD4C70C8DC534304294C9 /* InlineSuggestionInstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineSuggestionInstaller.swift; sourceTree = ""; }; 9E574E2A2735328331FC3C78 /* CloseOnCmdW.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseOnCmdW.swift; sourceTree = ""; }; 9E67CF5D8552AE1F51386161 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9E982534C8CF6270BCF39106 /* ReviewWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewWindow.swift; sourceTree = ""; }; A8C269A6E9AE87961C3356DC /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; + ABF0871355239B5D7C80B719 /* web-remote.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = "web-remote.css"; sourceTree = ""; }; + B43EEEA275BAC1522032064C /* PerformanceRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceRegressionTests.swift; sourceTree = ""; }; + B7CBC5C072019BC8A7C08327 /* web-remote-index.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-remote-index.html"; sourceTree = ""; }; B957E30052DD894C5B6F1063 /* terminfo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = terminfo; path = GhosttyResources/terminfo; sourceTree = SOURCE_ROOT; }; + BD31B21D1D50D85D5D5E366A /* xterm-LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "xterm-LICENSE.txt"; sourceTree = ""; }; C0656F7C1D48AE0429C1888B /* VisualEffectBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectBackground.swift; sourceTree = ""; }; C11B3335E47DDAF77A883064 /* ModalOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalOverlay.swift; sourceTree = ""; }; C6FB2646BF84D9DCBB89FC93 /* GitRepositoryWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitRepositoryWatcher.swift; sourceTree = ""; }; @@ -166,15 +196,19 @@ D03AFC613028C5BB0D99238D /* zsh-autosuggestions.zsh */ = {isa = PBXFileReference; path = "zsh-autosuggestions.zsh"; sourceTree = ""; }; D7F8C1899F93B387843907FF /* ReviewScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewScopeTests.swift; sourceTree = ""; }; DB20921E3CAC1006A8FBD3AE /* SettingsSafety.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSafety.swift; sourceTree = ""; }; + DEEB37F39E654EF9574D8098 /* WebRemoteProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRemoteProtocolTests.swift; sourceTree = ""; }; E298AD81B1615CBBFD69F48A /* GhosttySurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttySurfaceView.swift; sourceTree = ""; }; E94AB23640CA1E6007F8B2B4 /* GlintTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GlintTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; EBB695363FAF3A996EF0C9FF /* GitRefreshCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitRefreshCoordinator.swift; sourceTree = ""; }; EBCC04C19D745AA867D961FD /* PaneAgentKindTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneAgentKindTests.swift; sourceTree = ""; }; + F3AFEC99ADB408EF21B6530E /* WebRemoteServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRemoteServer.swift; sourceTree = ""; }; F3B91F000C01FEA1C7E61F5A /* ShellRcBlockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellRcBlockTests.swift; sourceTree = ""; }; F3EB9A1E80F3CC4BCA468774 /* AgentChoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentChoice.swift; sourceTree = ""; }; F5A69ACF962AEDE189F98649 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + F6DDF70810465204EB990667 /* crypto.mjs */ = {isa = PBXFileReference; path = crypto.mjs; sourceTree = ""; }; F8A1F24DAA7220AD4EE961E2 /* UsageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageStore.swift; sourceTree = ""; }; FC6FFB5185C403F6F44A1687 /* GhosttyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyManager.swift; sourceTree = ""; }; + FDE16D678B1BC67A99EBA74F /* OmpHookInstallerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmpHookInstallerTests.swift; sourceTree = ""; }; FF0082D535969C315B8D75D7 /* AgentHookRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentHookRoutingTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -191,6 +225,26 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1373E19D4E0CF52B745B7F83 /* WebRemote */ = { + isa = PBXGroup; + children = ( + 677808A57BE53607DA391FC8 /* addon-fit-LICENSE.txt */, + 5DB06D8C68CA7B9C7E1E83E7 /* addon-fit.mjs */, + 2C85E16653B066382F22FEDD /* crypto-LICENSE.txt */, + F6DDF70810465204EB990667 /* crypto.mjs */, + 2B1F1216422BE2A4234ABA44 /* nerd-fonts-LICENSE.txt */, + 5627A3DA0ADBDC6194E2D7A7 /* symbols-nerd-font-LICENSE.txt */, + 911638005A9881330F4F855F /* SymbolsNerdFontMono-Regular.ttf */, + B7CBC5C072019BC8A7C08327 /* web-remote-index.html */, + ABF0871355239B5D7C80B719 /* web-remote.css */, + 07C2CEDE2C717743C29E6026 /* web-remote.js */, + BD31B21D1D50D85D5D5E366A /* xterm-LICENSE.txt */, + 49C5AC93BE947131B5611EF8 /* xterm.css */, + 36D0A3B6CB143C412B8EDA7B /* xterm.mjs */, + ); + path = WebRemote; + sourceTree = ""; + }; 169D300DD0731BE0FC20E230 /* Resources */ = { isa = PBXGroup; children = ( @@ -201,6 +255,7 @@ 376568363043C189904E4E0A /* Localizable.xcstrings */, 6860F8F626EFDB9A289FC727 /* themes.json */, A4271BA2785D3733E2F3908E /* ShellIntegration */, + 1373E19D4E0CF52B745B7F83 /* WebRemote */, ); path = Resources; sourceTree = ""; @@ -217,6 +272,15 @@ path = Pane; sourceTree = ""; }; + 5F19F1E24EF052A6FE3FBFA7 /* WebRemote */ = { + isa = PBXGroup; + children = ( + 943254F613D20C4F0941EFA6 /* WebRemoteProtocol.swift */, + F3AFEC99ADB408EF21B6530E /* WebRemoteServer.swift */, + ); + path = WebRemote; + sourceTree = ""; + }; 5F9010EBEE9DCDE13D866A74 /* GlintTests */ = { isa = PBXGroup; children = ( @@ -226,22 +290,24 @@ 26141150D2DB4C0589558ECC /* CodexHookInstallerTests.swift */, 0FBF22AEC81DBE92987A8CB4 /* CodexUsageReaderTests.swift */, 0F58E2127BAE2C9F5155CF07 /* DevinHookInstallerTests.swift */, - 1228ACED02A84EE4A7AE5C78 /* OmpHookInstallerTests.swift */, - FFAAA8D93863E47CBF04F37C /* GrokHookInstallerTests.swift */, 0D751E30DFF2EFA6F705A374 /* GhosttyTickSchedulerTests.swift */, 65BE76E8E627A5347D565968 /* GitRefreshCoordinatorTests.swift */, 49C888E59D1091BFC22A9EA4 /* GitRepositoryWatcherTests.swift */, + 5BDAAD70DC4C2FC80F6DEA51 /* GrokHookInstallerTests.swift */, 349714D49AE52D84995314FD /* MascotAssetTests.swift */, + FDE16D678B1BC67A99EBA74F /* OmpHookInstallerTests.swift */, EBCC04C19D745AA867D961FD /* PaneAgentKindTests.swift */, 797C4E86D052EF8A68DD9B39 /* PaneSessionIdMigrationTests.swift */, + B43EEEA275BAC1522032064C /* PerformanceRegressionTests.swift */, 51DE2D7740BD03498A936B62 /* PersistenceRecoveryTests.swift */, - D7A100000000000000000001 /* PerformanceRegressionTests.swift */, 4A0B24E791FF6CEB30A9DDD5 /* RemoteTitleParserTests.swift */, 2CFFB91CE5C44976A6961F1F /* ReviewDiffParsingTests.swift */, D7F8C1899F93B387843907FF /* ReviewScopeTests.swift */, F3B91F000C01FEA1C7E61F5A /* ShellRcBlockTests.swift */, 4B8EDEAA2D0EAD42ED6F1915 /* SSHGitRunnerTests.swift */, 3FAD36B4BDB22AB47353C569 /* SyntaxHighlighterTests.swift */, + DEEB37F39E654EF9574D8098 /* WebRemoteProtocolTests.swift */, + 45C96BF92C4618FF97D68919 /* WebRemoteServerIntegrationTests.swift */, 83E4DF4C5180D50C381B4976 /* WorkspaceIconKindTests.swift */, ); path = GlintTests; @@ -350,6 +416,7 @@ 5D84BADE56807E092D26B87B /* Pane */, 169D300DD0731BE0FC20E230 /* Resources */, A212D40EB74F607990A5942B /* Review */, + 5F19F1E24EF052A6FE3FBFA7 /* WebRemote */, 976DA38BD8100EAB5D19A251 /* Workspace */, ); path = Glint; @@ -485,9 +552,22 @@ A91EFEF0FEA392A0745724DE /* AppIcon.icon in Resources */, 942ED7C1C1A2E823BA52F67A /* Assets.xcassets in Resources */, FCB67525DF16A4DCF9B2AF7A /* Localizable.xcstrings in Resources */, + 09AAB5DF2BA70FCAADAB4C2C /* SymbolsNerdFontMono-Regular.ttf in Resources */, + 632C9446752A79B9C95A5D0D /* addon-fit-LICENSE.txt in Resources */, + 9A5EC6D5F8F65EE762FCD917 /* addon-fit.mjs in Resources */, + EE6D05AB1D935F502865ABDA /* crypto-LICENSE.txt in Resources */, + FB4C8529271BF52873745556 /* crypto.mjs in Resources */, 262CB25BEB2B28B12C162F66 /* ghostty in Resources */, + 3B439B6978244B06B9695824 /* nerd-fonts-LICENSE.txt in Resources */, + 7E6DBB055150A4D140351EB6 /* symbols-nerd-font-LICENSE.txt in Resources */, 0D3661ECB91851F77B1E48CE /* terminfo in Resources */, 47BB527385B84454ABF8FC40 /* themes.json in Resources */, + D6538EB0E59B35FB96AD80E2 /* web-remote-index.html in Resources */, + 6B6B183329BCCDE5474D88AF /* web-remote.css in Resources */, + 432148F665B867944BD134BF /* web-remote.js in Resources */, + 79467AAA8C1E5D47194A8173 /* xterm-LICENSE.txt in Resources */, + 6EE719F5F1D8C81751C74445 /* xterm.css in Resources */, + 6BE78104E4CC97417ED400B4 /* xterm.mjs in Resources */, 2DD19A3FDAE094EF32B0B9F0 /* zsh-autosuggestions.zsh in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -499,21 +579,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D7A100000000000000000002 /* PerformanceRegressionTests.swift in Sources */, A019A2AD685F1407D7E2A7E1 /* AgentHookRoutingTests.swift in Sources */, 81532B36FEC3039B8EAD9CB1 /* AgentKindResolutionTests.swift in Sources */, C41119D04864077E5B554F59 /* CodexHomeStoreTests.swift in Sources */, 0FEECB27EB1ACE11E29ECCEF /* CodexHookInstallerTests.swift in Sources */, FEBC3158CB471E9ABDF931BB /* CodexUsageReaderTests.swift in Sources */, 34997DD4A4F9B64A754FE77F /* DevinHookInstallerTests.swift in Sources */, - 3521283B78CD4A34A8725243 /* OmpHookInstallerTests.swift in Sources */, - DEA00336B2F4043FD6D527F2 /* GrokHookInstallerTests.swift in Sources */, 180444BD20493AE4822BD372 /* GhosttyTickSchedulerTests.swift in Sources */, 2D9012046800A06555DEA381 /* GitRefreshCoordinatorTests.swift in Sources */, 0FB9792E290FC2A8B527B2D0 /* GitRepositoryWatcherTests.swift in Sources */, + 57D99FDEAF8596DCDBC55ABE /* GrokHookInstallerTests.swift in Sources */, 04E75EAD5165E8D96C1C5517 /* MascotAssetTests.swift in Sources */, + DFDF7E690BD53E885484EA00 /* OmpHookInstallerTests.swift in Sources */, ED46CD7A6E09551A775AD3B8 /* PaneAgentKindTests.swift in Sources */, 3194A1545E36E400BC2C33E6 /* PaneSessionIdMigrationTests.swift in Sources */, + 4EC47B8DA99A679D88935CB8 /* PerformanceRegressionTests.swift in Sources */, E80809A987A07731FF35D01D /* PersistenceRecoveryTests.swift in Sources */, AB3CF36FE1CF2E35AC6ADC26 /* RemoteTitleParserTests.swift in Sources */, F5AE37B67B17DD61B652E5B6 /* ReviewDiffParsingTests.swift in Sources */, @@ -521,6 +601,8 @@ BFF9A1736CA029241BB0622D /* SSHGitRunnerTests.swift in Sources */, 4ED4428EEC2AFB777092FAEF /* ShellRcBlockTests.swift in Sources */, FAB0828152636BBBBC5989CD /* SyntaxHighlighterTests.swift in Sources */, + B3EEBFD73C01D337FA3369BD /* WebRemoteProtocolTests.swift in Sources */, + 44C33E4DC10425D7555AC4E2 /* WebRemoteServerIntegrationTests.swift in Sources */, C748DE21F23B49B2FFD73DC7 /* WorkspaceIconKindTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -571,6 +653,8 @@ 692D1A7D490E07D796535EAA /* UpdaterController.swift in Sources */, D3D6CF13B67D30E9DA557F83 /* UsageStore.swift in Sources */, AB89FA6EF5785EC4026F4402 /* VisualEffectBackground.swift in Sources */, + EAC3EE05466931E8AAF620D2 /* WebRemoteProtocol.swift in Sources */, + FF24B6DE3A9ED42C8C68D16C /* WebRemoteServer.swift in Sources */, 370FA310A4B7CC8489BF51DE /* WhatsNewView.swift in Sources */, D1BDE1E5DE8D4FE1F96822D1 /* WorkspaceStore.swift in Sources */, ); diff --git a/Glint/Chrome/SettingsView.swift b/Glint/Chrome/SettingsView.swift index 604b762..5231e3b 100644 --- a/Glint/Chrome/SettingsView.swift +++ b/Glint/Chrome/SettingsView.swift @@ -1,3 +1,4 @@ +import AppKit import SwiftUI /// Settings window. Split into a left category sidebar and a right @@ -968,8 +969,22 @@ private struct ThemeBrowserRow: View { } private struct TerminalPane: View { + private struct WebRemoteAlert: Identifiable { + enum Kind { + case failure(String) + case portConflict(UInt16) + case resetComplete(UInt16) + } + + let id = UUID() + let kind: Kind + } + @EnvironmentObject var store: WorkspaceStore @State private var shellKeybindsInstallFailed = false + @State private var confirmingWebRemoteKeyReset = false + @State private var resettingWebRemoteCredentials = false + @State private var webRemoteAlert: WebRemoteAlert? private let scrollbackSizeChoices: [Int] = [5, 10, 25, 50, 100, 250] .map { $0 * 1_000_000 } @@ -1110,6 +1125,234 @@ private struct TerminalPane: View { .toggleStyle(.switch).labelsHidden() } } + + SettingsCard("Web remote control", + footer: "Serves Glint's bundled browser terminal on this Mac for trusted LAN or VPN use. The copied link or access key grants terminal input; traffic is not TLS-encrypted. Off by default.") { + SettingsRow("Allow browser control", subtitle: webRemoteStatusText) { + Toggle("", isOn: $store.webRemoteEnabled) + .toggleStyle(.switch).labelsHidden() + } + if store.webRemoteEnabled { + SettingsDivider() + SettingsRow("Listen on", subtitle: webRemoteListenSubtitle) { + HStack(spacing: 6) { + Picker("Listen on", selection: $store.webRemoteListenInterface) { + Text("Localhost only").tag(WebRemoteListenTarget.loopback) + ForEach(store.webRemoteInterfaceOptions) { iface in + Text(verbatim: "\(iface.name) (\(iface.address))").tag(iface.name) + } + Text("All interfaces (less secure)").tag(WebRemoteListenTarget.any) + } + .pickerStyle(.menu) + .labelsHidden() + Button(action: { store.refreshWebRemoteInterfaces() }) { + Image(systemName: "arrow.clockwise") + } + .buttonStyle(.borderless) + .help("Refresh interfaces") + } + } + if WebRemoteListenTarget.requiresActiveAttackWarning( + store.webRemoteListenInterface + ) { + SettingsDivider() + HStack(alignment: .top, spacing: 8) { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(Color.orange) + Text("Without TLS, encryption only blocks passive sniffing. An active attacker on this network can replace the web client and capture the access key or terminal input.") + .font(.system(size: 11)) + .foregroundStyle(Theme.text2) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.horizontal, 14) + .padding(.vertical, 10) + .background(Color.orange.opacity(0.055)) + } + if case let .portConflict(port) = store.webRemoteStatus { + SettingsDivider() + SettingsRow( + "Port conflict", + subtitle: String( + format: String(localized: "Port %d is already in use."), + Int(port) + ) + ) { + Button("Reset key and ports", role: .destructive) { + resetWebRemoteCredentials() + } + .controlSize(.small) + } + } + } + if !store.webRemoteAccessURLs.isEmpty { + SettingsDivider() + SettingsRow("Access URL", subtitle: displayURL(store.webRemoteAccessURLs[0])) { + if store.webRemoteAccessURLs.count == 1 { + Button("Copy link") { + copyWebRemoteURL(store.webRemoteAccessURLs[0]) + } + .controlSize(.small) + } else { + Menu("Copy link") { + ForEach(store.webRemoteAccessURLs, id: \.self) { url in + Button(displayURL(url)) { + copyWebRemoteURL(url) + } + } + } + .controlSize(.small) + } + } + if let key = store.webRemoteAccessKey { + SettingsDivider() + SettingsRow("Access key", subtitle: abbreviatedAccessKey(key)) { + HStack(spacing: 8) { + Button("Copy key") { + copyWebRemoteValue(key) + } + Button("Reset key and ports", role: .destructive) { + confirmingWebRemoteKeyReset = true + } + } + .controlSize(.small) + } + } + } + } + .confirmationDialog( + "Reset access key and ports?", + isPresented: $confirmingWebRemoteKeyReset, + titleVisibility: .visible + ) { + Button("Reset key and ports", role: .destructive) { + resetWebRemoteCredentials() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("Connected browsers will be disconnected. A new key and ports will be generated, so old links will stop working.") + } + .onAppear { + presentWebRemoteAlert(for: store.webRemoteStatus) + } + .onChange(of: store.webRemoteStatus) { _, status in + presentWebRemoteAlert(for: status) + } + .alert(item: $webRemoteAlert) { alert in + switch alert.kind { + case let .failure(message): + return Alert( + title: Text("Web remote control unavailable"), + message: Text(message), + dismissButton: .default(Text("OK")) + ) + case let .portConflict(port): + return Alert( + title: Text("Web remote port conflict"), + message: Text( + String( + format: String(localized: "Port %d is already in use. Resetting will generate a new key and ports, disconnect browsers, and invalidate old links."), + Int(port) + ) + ), + primaryButton: .destructive(Text("Reset key and ports")) { + resetWebRemoteCredentials() + }, + secondaryButton: .cancel() + ) + case let .resetComplete(port): + return Alert( + title: Text("Access key and ports reset"), + message: Text( + String( + format: String(localized: "New ports: %d (web) and %d (terminal). Copy the new link and key to reconnect."), + Int(port), Int(port) + 1 + ) + ), + dismissButton: .default(Text("OK")) + ) + } + } + } + + private var webRemoteStatusText: String { + switch store.webRemoteStatus { + case .stopped: + return String(localized: "Off — no network ports are bound.") + case .starting: + return String(localized: "Starting the local web server…") + case .ready: + return String(localized: "Ready — copy a session link to another browser.") + case let .portConflict(port): + return String( + format: String(localized: "Port %d is already in use."), + Int(port) + ) + case let .failed(message): + return String( + format: String(localized: "Could not start: %@"), + message + ) + } + } + + private func resetWebRemoteCredentials() { + resettingWebRemoteCredentials = true + store.resetWebRemoteCredentials() + } + + private func presentWebRemoteAlert(for status: WebRemoteStatus) { + switch status { + case let .portConflict(port): + resettingWebRemoteCredentials = false + webRemoteAlert = WebRemoteAlert(kind: .portConflict(port)) + case let .failed(message): + resettingWebRemoteCredentials = false + webRemoteAlert = WebRemoteAlert(kind: .failure(message)) + case let .ready(urls) where resettingWebRemoteCredentials: + resettingWebRemoteCredentials = false + guard let url = urls.first, + let port = URLComponents(string: url)?.port, + let httpPort = UInt16(exactly: port) + else { return } + webRemoteAlert = WebRemoteAlert(kind: .resetComplete(httpPort)) + default: + break + } + } + + private var webRemoteListenSubtitle: String { + switch store.webRemoteListenInterface { + case WebRemoteListenTarget.loopback: + return String(localized: "Only this Mac (127.0.0.1) can connect.") + case WebRemoteListenTarget.any: + return String(localized: "Reachable from any network this Mac joins (least secure).") + default: + if let iface = store.webRemoteInterfaceOptions.first(where: { $0.name == store.webRemoteListenInterface }) { + return iface.address + } + return String(localized: "Selected interface is no longer available. Pick another or use All interfaces.") + } + } + + private func displayURL(_ value: String) -> String { + WebRemoteAccessURL.redacted(from: value) + } + + private func abbreviatedAccessKey(_ value: String) -> String { + guard value.count > 16 else { return value } + return "\(value.prefix(8))…\(value.suffix(8))" + } + + private func copyWebRemoteURL(_ value: String) { + // Strip the #token= fragment so the secret isn't written to the + // clipboard alongside the address. The key is copied separately via + // the "Access key" row. + copyWebRemoteValue(WebRemoteAccessURL.redacted(from: value)) + } + + private func copyWebRemoteValue(_ value: String) { + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(value, forType: .string) } private func scrollbackSizeLabel(for bytes: Int) -> String { diff --git a/Glint/Pane/GhosttySurfaceView.swift b/Glint/Pane/GhosttySurfaceView.swift index c00006d..8598f86 100644 --- a/Glint/Pane/GhosttySurfaceView.swift +++ b/Glint/Pane/GhosttySurfaceView.swift @@ -176,7 +176,10 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { required init?(coder: NSCoder) { fatalError("not used") } deinit { - if let s = surface { ghostty_surface_free(s) } + if let s = surface { + ghostty_surface_set_pty_tee_v2_cb(s, nil, nil) + ghostty_surface_free(s) + } } /// One-shot: a kept-alive surface was re-attached to a window, so the next @@ -185,6 +188,9 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { /// occlusion transition, neither of which a same-size workspace switch-back /// produces. Cleared the moment that forced frame is drawn. private var pendingVisibleRedraw = false + /// While a browser controls this pane, size the PTY grid for the browser + /// instead of the narrower AppKit pane. Releasing control restores bounds. + private var webRemoteGridSize: WebRemoteTerminalSize? /// Opt-in tracing for the blank-pane-on-switch investigation. Launch the dev /// build with `GLINT_LOG_VISIBLE=1` to see attach / forced-redraw events. @@ -405,6 +411,11 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { return } self.surface = s + ghostty_surface_set_pty_tee_v2_cb( + s, + webRemotePTYTeeCallback, + Unmanaged.passUnretained(self).toOpaque() + ) focusUpdateGate.reset() pendingInitialInput = nil isTakenOffline = false @@ -552,11 +563,36 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { let cell_width: Int let text: String } + struct Cursor: Decodable { + let row: Int + let column: Int + let visible: Bool + let style: String + let blinking: Bool + } + struct Mode: Decodable { + let code: Int + let ansi: Bool + let on: Bool + } + struct ScrollingRegion: Decodable { + let top: Int + let bottom: Int + let left: Int + let right: Int + } + let columns: Int? let styles: [Style] let row_spans: [Span] let scrollback_spans: [Span] let scrollback_rows: Int let rows: Int + let cursor: Cursor? + let active_screen: String? + let modes: [Mode]? + let scrolling_region: ScrollingRegion? + let pty_output_seq: UInt64? + let pty_stream_safe: Bool? // Per-row soft-wrap flags (true = row continues into the next). Optional // so snapshots written before this field still decode. let row_wraps: [Bool]? @@ -736,6 +772,51 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { return text.isEmpty ? nil : text } + static func webRemoteSnapshotPayload(fromRenderGrid data: Data, maxLines: Int) -> Data? { + guard let grid = try? JSONDecoder().decode(RenderGrid.self, from: data) else { return nil } + let ansi = reconstructANSI(fromRenderGrid: data, maxLines: maxLines) + guard let columns = grid.columns, + let cursor = grid.cursor, + let activeScreen = grid.active_screen.flatMap(WebRemoteTerminalState.Screen.init(rawValue:)), + let modes = grid.modes + else { return WebRemoteSnapshotPayload.make(ansi: ansi) } + + let scrollingRegion = grid.scrolling_region.map { + WebRemoteTerminalState.ScrollingRegion( + top: $0.top, + bottom: $0.bottom, + left: $0.left, + right: $0.right + ) + } + let state = WebRemoteTerminalState( + columns: columns, + rows: grid.rows, + activeScreen: activeScreen, + modes: modes.map { + WebRemoteTerminalState.Mode(code: $0.code, ansi: $0.ansi, on: $0.on) + }, + scrollingRegion: scrollingRegion, + cursor: WebRemoteTerminalState.Cursor( + row: cursor.row, + column: cursor.column, + visible: cursor.visible, + style: WebRemoteTerminalState.Cursor.Style(rawValue: cursor.style) ?? .block, + blinking: cursor.blinking + ) + ) + return WebRemoteSnapshotPayload.make(ansi: ansi, state: state) + } + + static func webRemoteSnapshot(fromRenderGrid data: Data, maxLines: Int) -> WebRemoteTerminalSnapshot? { + guard let grid = try? JSONDecoder().decode(RenderGrid.self, from: data), + grid.pty_stream_safe == true, + let outputSequence = grid.pty_output_seq, + let payload = webRemoteSnapshotPayload(fromRenderGrid: data, maxLines: maxLines) + else { return nil } + return WebRemoteTerminalSnapshot(payload: payload, outputSequence: outputSequence) + } + // MARK: - surface creation failure UI /// Centered "Terminal failed to start" + Retry, shown when ghostty @@ -908,6 +989,7 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { ScrollbackArchive.drain() } + ghostty_surface_set_pty_tee_v2_cb(s, nil, nil) surface = nil ghostty_surface_free(s) pendingVisibleRedraw = false @@ -1032,6 +1114,13 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { return true } + @discardableResult + func ensureLiveForWebRemoteControl() -> Bool { + guard ensureLiveSurface() else { return false } + inactiveSince = nil + return true + } + /// Best-effort current cwd: prefers the cached value pushed via ghostty's /// PWD action; falls back to proc_pidinfo polling. func currentCwd() -> String? { @@ -1353,6 +1442,26 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { let pixelHeight = floor(pointsSize.height * scale) guard pixelWidth > 0, pixelHeight > 0 else { return } + var targetWidth = UInt32(pixelWidth) + var targetHeight = UInt32(pixelHeight) + if let remoteSize = webRemoteGridSize { + let current = ghostty_surface_size(s) + let cellWidth = Int(current.cell_width_px) + let cellHeight = Int(current.cell_height_px) + if cellWidth > 0, cellHeight > 0 { + let horizontalRemainder = max( + 0, + Int(current.width_px) - Int(current.columns) * cellWidth + ) + let verticalRemainder = max( + 0, + Int(current.height_px) - Int(current.rows) * cellHeight + ) + targetWidth = UInt32(remoteSize.columns * cellWidth + horizontalRemainder) + targetHeight = UInt32(remoteSize.rows * cellHeight + verticalRemainder) + } + } + CATransaction.begin() CATransaction.setDisableActions(true) // ghostty's IOSurfaceLayer only learns the scale at surface creation; @@ -1360,7 +1469,7 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { // at the right DPI. It reads layer.contentsScale when sizing frames. layer?.contentsScale = scale ghostty_surface_set_content_scale(s, scale, scale) - ghostty_surface_set_size(s, UInt32(pixelWidth), UInt32(pixelHeight)) + ghostty_surface_set_size(s, targetWidth, targetHeight) CATransaction.commit() // Echo restored history only once the surface width has SETTLED. The @@ -2162,6 +2271,51 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { } } + func setWebRemoteGridSize(_ size: WebRemoteTerminalSize) { + guard surface != nil, webRemoteGridSize != size else { return } + webRemoteGridSize = size + syncSurfaceSize(pointsSize: bounds.size) + } + + func releaseWebRemoteGridSize() { + guard webRemoteGridSize != nil else { return } + webRemoteGridSize = nil + syncSurfaceSize(pointsSize: bounds.size) + } + + func injectRemoteInput(_ data: Data) { + guard let s = surface, !data.isEmpty else { return } + noteUserInputForIdleTracking() + data.withUnsafeBytes { raw in + guard let bytes = raw.bindMemory(to: CChar.self).baseAddress else { return } + ghostty_surface_text_input(s, bytes, UInt(data.count)) + } + markScrollbackDirty() + } + + func webRemoteSnapshot(maxLines: Int = 1000) -> WebRemoteTerminalSnapshot? { + guard let s = surface else { return nil } + let json = ghostty_surface_render_grid_json(s, "", 0, 0, UInt(maxLines)) + defer { ghostty_string_free(json) } + guard let pointer = json.ptr, json.len > 0 else { return nil } + let grid = Data(bytes: pointer, count: Int(json.len)) + return Self.webRemoteSnapshot(fromRenderGrid: grid, maxLines: maxLines) + } + + fileprivate func forwardWebRemoteOutput( + _ bytes: UnsafePointer?, + count: UInt, + sequence: UInt64 + ) { + guard let paneKey else { return } + WebRemoteServer.shared.forwardTerminalOutput( + pane: paneKey, + bytes: bytes, + count: count, + sequence: sequence + ) + } + /// Inject a single whitelisted key. Special keys go through /// `ghostty_surface_key` (press+release) so ghostty maps them to the right /// escape sequence; printable chars go straight through the text-input pipe @@ -2500,6 +2654,18 @@ final class GhosttySurfaceView: NSView, NSTextInputClient { } } +private let webRemotePTYTeeCallback: @convention(c) ( + UnsafeMutableRawPointer?, + UnsafePointer?, + UInt, + UInt64 +) -> Void = { userData, bytes, count, sequence in + guard let userData, let bytes else { return } + let view = Unmanaged.fromOpaque(userData).takeUnretainedValue() + let unsigned = UnsafeRawPointer(bytes).assumingMemoryBound(to: UInt8.self) + view.forwardWebRemoteOutput(unsigned, count: count, sequence: sequence) +} + // MARK: - Terminal scrollback restore (colored snapshot via render_grid_json) /// On-disk store for per-pane scrollback snapshots (ANSI-colored text). Writes diff --git a/Glint/Pane/PaneView.swift b/Glint/Pane/PaneView.swift index 36aa12d..d978a4f 100644 --- a/Glint/Pane/PaneView.swift +++ b/Glint/Pane/PaneView.swift @@ -65,6 +65,32 @@ struct PaneView: View { Color.black.opacity(unfocusedDimOpacity) .allowsHitTesting(false) } + if store.isWebRemoteControlled(workspaceID: workspaceID, paneID: paneID) { + VStack { + VStack(alignment: .leading, spacing: 2) { + Label("Controlled from web", systemImage: "network") + .font(.system(size: 10.5, weight: .semibold)) + .foregroundStyle(Theme.text1) + Text("Layout may be inaccurate while controlled. It will restore automatically after disconnecting.") + .font(.system(size: 9.5, weight: .medium)) + .foregroundStyle(Theme.text3) + } + .padding(.horizontal, 10) + .padding(.vertical, 7) + .background( + RoundedRectangle(cornerRadius: 9, style: .continuous) + .fill(Theme.bgPane.opacity(0.94)) + ) + .overlay( + RoundedRectangle(cornerRadius: 9, style: .continuous) + .stroke(Theme.overlay(0.12), lineWidth: 1) + ) + .shadow(color: Color.black.opacity(0.22), radius: 8, y: 3) + .padding(.top, 8) + Spacer() + } + .allowsHitTesting(false) + } } .contentShape(Rectangle()) .onTapGesture { store.focus(paneID) } diff --git a/Glint/Resources/Info.plist b/Glint/Resources/Info.plist index 999075b..3be39fc 100644 --- a/Glint/Resources/Info.plist +++ b/Glint/Resources/Info.plist @@ -31,6 +31,8 @@ $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright © 2026 Glint + NSLocalNetworkUsageDescription + Glint uses your local network to let authenticated devices control shared terminals. NSPrincipalClass NSApplication SUEnableAutomaticChecks diff --git a/Glint/Resources/Localizable.xcstrings b/Glint/Resources/Localizable.xcstrings index edb5a1a..d120d53 100644 --- a/Glint/Resources/Localizable.xcstrings +++ b/Glint/Resources/Localizable.xcstrings @@ -421,6 +421,259 @@ } } }, + "Access key": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "访问密钥" + } + } + } + }, + "Access URL": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "访问地址" + } + } + } + }, + "Allow browser control": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许浏览器控制" + } + } + } + }, + "Copy key": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "复制密钥" + } + } + } + }, + "Copy link": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "复制链接" + } + } + } + }, + "Could not start: %@": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法启动:%@" + } + } + } + }, + "Access key and ports reset": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "访问密钥和端口已重置" + } + } + } + }, + "New ports: %d (web) and %d (terminal). Copy the new link and key to reconnect.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新端口:%d(网页)和 %d(终端)。请复制新链接和密钥重新连接。" + } + } + } + }, + "Port %d is already in use.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "端口 %d 已被占用。" + } + } + } + }, + "Port %d is already in use. Resetting will generate a new key and ports, disconnect browsers, and invalidate old links.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "端口 %d 已被占用。重置会生成新密钥和端口、断开浏览器,并使旧链接失效。" + } + } + } + }, + "Port conflict": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "端口冲突" + } + } + } + }, + "Web remote control unavailable": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "网页远程控制不可用" + } + } + } + }, + "Web remote port conflict": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "网页远程控制端口冲突" + } + } + } + }, + "Controlled from web": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在通过网页控制" + } + } + } + }, + "Layout may be inaccurate while controlled. It will restore automatically after disconnecting.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "控制期间布局可能不准确,断开后会自动恢复。" + } + } + } + }, + "Off — no network ports are bound.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已关闭 — 未监听任何网络端口。" + } + } + } + }, + "Ready — copy a session link to another browser.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已就绪 — 可复制会话链接到其他浏览器。" + } + } + } + }, + "Reset access key and ports?": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重置访问密钥和端口?" + } + } + } + }, + "Reset key and ports": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重置密钥和端口" + } + } + } + }, + "Connected browsers will be disconnected. A new key and ports will be generated, so old links will stop working.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已连接的浏览器将断开。系统会生成新密钥和端口,旧链接将失效。" + } + } + } + }, + "Serves Glint's bundled browser terminal on this Mac for trusted LAN or VPN use. The copied link or access key grants terminal input; traffic is not TLS-encrypted. Off by default.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在这台 Mac 上提供 Glint 内置的浏览器终端,供可信的局域网或 VPN 使用。复制的链接或访问密钥可直接输入终端;流量未使用 TLS 加密。默认关闭。" + } + } + } + }, + "Starting the local web server…": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在启动本地网页服务器…" + } + } + } + }, + "Web remote control": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "网页远程控制" + } + } + } + }, "App icon": { "extractionState": "manual", "localizations": { @@ -5459,6 +5712,94 @@ } } } + }, + "Listen on": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "监听网卡" + } + } + } + }, + "Localhost only": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "仅本机" + } + } + } + }, + "Only this Mac (127.0.0.1) can connect.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "仅本机(127.0.0.1)可连接。" + } + } + } + }, + "Reachable from any network this Mac joins (least secure).": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "本机加入的任何网络均可访问(最不安全)。" + } + } + } + }, + "Without TLS, encryption only blocks passive sniffing. An active attacker on this network can replace the web client and capture the access key or terminal input.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未使用 TLS 时,加密仅能防止被动嗅探。同一网络中的主动攻击者可以替换网页客户端,窃取访问密钥或终端输入。" + } + } + } + }, + "All interfaces (less secure)": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全部网卡(较不安全)" + } + } + } + }, + "Refresh interfaces": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "刷新网卡列表" + } + } + } + }, + "Selected interface is no longer available. Pick another or use All interfaces.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "所选网卡已不可用。请另选一个或使用“全部网卡”。" + } + } + } } }, "version": "1.0" diff --git a/Glint/Resources/WebRemote/SymbolsNerdFontMono-Regular.ttf b/Glint/Resources/WebRemote/SymbolsNerdFontMono-Regular.ttf new file mode 100644 index 0000000..d898eea Binary files /dev/null and b/Glint/Resources/WebRemote/SymbolsNerdFontMono-Regular.ttf differ diff --git a/Glint/Resources/WebRemote/addon-fit-LICENSE.txt b/Glint/Resources/WebRemote/addon-fit-LICENSE.txt new file mode 100644 index 0000000..8f17892 --- /dev/null +++ b/Glint/Resources/WebRemote/addon-fit-LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Glint/Resources/WebRemote/addon-fit.mjs b/Glint/Resources/WebRemote/addon-fit.mjs new file mode 100644 index 0000000..24d4f61 --- /dev/null +++ b/Glint/Resources/WebRemote/addon-fit.mjs @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved. + * @license MIT + * + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * @license MIT + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var h=2,_=1,o=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;let t=this._terminal._core;(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let t=this._terminal._core._renderService.dimensions;if(t.css.cell.width===0||t.css.cell.height===0)return;let s=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),l=parseInt(r.getPropertyValue("height")),a=Math.max(0,parseInt(r.getPropertyValue("width"))),i=window.getComputedStyle(this._terminal.element),n={top:parseInt(i.getPropertyValue("padding-top")),bottom:parseInt(i.getPropertyValue("padding-bottom")),right:parseInt(i.getPropertyValue("padding-right")),left:parseInt(i.getPropertyValue("padding-left"))},m=n.top+n.bottom,d=n.right+n.left,c=l-m,p=a-d-s;return{cols:Math.max(h,Math.floor(p/t.css.cell.width)),rows:Math.max(_,Math.floor(c/t.css.cell.height))}}};export{o as FitAddon}; +//# sourceMappingURL=addon-fit.mjs.map diff --git a/Glint/Resources/WebRemote/crypto-LICENSE.txt b/Glint/Resources/WebRemote/crypto-LICENSE.txt new file mode 100644 index 0000000..a88c86f --- /dev/null +++ b/Glint/Resources/WebRemote/crypto-LICENSE.txt @@ -0,0 +1,57 @@ +Glint web-remote ships a bundled crypto module (crypto.mjs) derived from the +following MIT-licensed packages. Both texts are reproduced below in full. + +Bundle contents: HMAC-SHA256, HKDF (extract + expand), AES-256-GCM. + +================================================================================ +@noble/hashes — https://github.com/paulmillr/noble-hashes +================================================================================ + +The MIT License (MIT) + +Copyright (c) 2022 Paul Miller (https://paulmillr.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +================================================================================ +@noble/ciphers — https://github.com/paulmillr/noble-ciphers +================================================================================ + +The MIT License (MIT) + +Copyright (c) 2022 Paul Miller (https://paulmillr.com) +Copyright (c) 2016 Thomas Pornin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Glint/Resources/WebRemote/crypto.mjs b/Glint/Resources/WebRemote/crypto.mjs new file mode 100644 index 0000000..4ff6251 --- /dev/null +++ b/Glint/Resources/WebRemote/crypto.mjs @@ -0,0 +1,15 @@ +/*! Glint web-remote crypto bundle. + * Vendored primitives: HMAC-SHA256, HKDF (extract+expand), AES-256-GCM. + * Sources: @noble/hashes + @noble/ciphers (MIT, audited by Cure53; pure JS, no crypto.subtle). + * Rebuild: npm i @noble/hashes@1 @noble/ciphers@1 esbuild && \ + * esbuild entry.mjs --bundle --format=esm --minify --outfile=crypto.mjs + */ +function Ot(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function j(t){if(!Number.isSafeInteger(t)||t<0)throw new Error("positive integer expected, got "+t)}function K(t,...e){if(!Ot(t))throw new Error("Uint8Array expected");if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function G(t){if(typeof t!="function"||typeof t.create!="function")throw new Error("Hash should be wrapped by utils.createHasher");j(t.outputLen),j(t.blockLen)}function v(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function lt(t,e){K(t);let n=e.outputLen;if(t.length>>e}function kt(t){if(typeof t!="string")throw new Error("string expected");return new Uint8Array(new TextEncoder().encode(t))}function I(t){return typeof t=="string"&&(t=kt(t)),K(t),t}var k=class{};function dt(t){let e=o=>t().update(I(o)).digest(),n=t();return e.outputLen=n.outputLen,e.blockLen=n.blockLen,e.create=()=>t(),e}var R=class extends k{constructor(e,n){super(),this.finished=!1,this.destroyed=!1,G(e);let o=I(n);if(this.iHash=e.create(),typeof this.iHash.update!="function")throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let r=this.blockLen,s=new Uint8Array(r);s.set(o.length>r?e.create().update(o).digest():o);for(let c=0;cnew R(t,e).update(n).digest();F.create=(t,e)=>new R(t,e);function Kt(t,e,n,o){if(typeof t.setBigUint64=="function")return t.setBigUint64(e,n,o);let r=BigInt(32),s=BigInt(4294967295),c=Number(n>>r&s),f=Number(n&s),i=o?4:0,h=o?0:4;t.setUint32(e+i,c,o),t.setUint32(e+h,f,o)}function xt(t,e,n){return t&e^~t&n}function pt(t,e,n){return t&e^t&n^e&n}var Z=class extends k{constructor(e,n,o,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=e,this.outputLen=n,this.padOffset=o,this.isLE=r,this.buffer=new Uint8Array(e),this.view=P(this.buffer)}update(e){v(this),e=I(e),K(e);let{view:n,buffer:o,blockLen:r}=this,s=e.length;for(let c=0;cr-c&&(this.process(o,0),c=0);for(let a=c;au.length)throw new Error("_sha2: outputLen bigger than state");for(let a=0;a>>3,p=E(l,17)^E(l,19)^l>>>10;C[a]=p+C[a-7]+x+C[a-16]|0}let{A:o,B:r,C:s,D:c,E:f,F:i,G:h,H:u}=this;for(let a=0;a<64;a++){let d=E(f,6)^E(f,11)^E(f,25),l=u+d+xt(f,i,h)+vt[a]+C[a]|0,p=(E(o,2)^E(o,13)^E(o,22))+pt(o,r,s)|0;u=h,h=i,i=f,f=c+l|0,c=s,s=r,r=o,o=l+p|0}o=o+this.A|0,r=r+this.B|0,s=s+this.C|0,c=c+this.D|0,f=f+this.E|0,i=i+this.F|0,h=h+this.G|0,u=u+this.H|0,this.set(o,r,s,c,f,i,h,u)}roundClean(){U(C)}destroy(){this.set(0,0,0,0,0,0,0,0),U(this.buffer)}};var bt=dt(()=>new Y);var q=bt;function Ft(t,e,n){return G(t),n===void 0&&(n=new Uint8Array(t.outputLen)),F(t,I(n),I(e))}var nt=Uint8Array.from([0]),gt=Uint8Array.of();function ot(t,e,n,o=32){G(t),j(o);let r=t.outputLen;if(o>255*r)throw new Error("Length should be <= 255*HashLen");let s=Math.ceil(o/r);n===void 0&&(n=gt);let c=new Uint8Array(s*r),f=F.create(t,e),i=f._cloneInto(),h=new Uint8Array(f.outputLen);for(let u=0;uot(t,Ft(t,e,n),o,r);function wt(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function Dt(t){if(typeof t!="boolean")throw new Error(`boolean expected, not ${t}`)}function g(t,...e){if(!wt(t))throw new Error("Uint8Array expected");if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function W(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function rt(t,e){g(t);let n=e.outputLen;if(t.length{function n(o,...r){if(g(o),!Vt)throw new Error("Non little-endian hardware is not yet supported");if(t.nonceLength!==void 0){let u=r[0];if(!u)throw new Error("nonce / iv required");t.varSizeNonce?g(u):g(u,t.nonceLength)}let s=t.tagLength;s&&r[1]!==void 0&&g(r[1]);let c=e(o,...r),f=(u,a)=>{if(a!==void 0){if(u!==2)throw new Error("cipher output not supported");g(a)}},i=!1;return{encrypt(u,a){if(i)throw new Error("cannot encrypt() twice with same key + nonce");return i=!0,g(u),f(c.encrypt.length,a),c.encrypt(u,a)},decrypt(u,a){if(g(u),s&&u.length>r&s),f=Number(n&s),i=o?4:0,h=o?0:4;t.setUint32(e+i,c,o),t.setUint32(e+h,f,o)}function Lt(t,e,n){Dt(n);let o=new Uint8Array(16),r=D(o);return J(r,0,BigInt(e),n),J(r,8,BigInt(t),n),o}function N(t){return t.byteOffset%4===0}function T(t){return Uint8Array.from(t)}var S=16,ct=new Uint8Array(16),H=B(ct),Gt=225,Wt=(t,e,n,o)=>{let r=o&1;return{s3:n<<31|o>>>1,s2:e<<31|n>>>1,s1:t<<31|e>>>1,s0:t>>>1^Gt<<24&-(r&1)}},w=t=>(t>>>0&255)<<24|(t>>>8&255)<<16|(t>>>16&255)<<8|t>>>24&255|0;function Nt(t){t.reverse();let e=t[15]&1,n=0;for(let o=0;o>>1|n,n=(r&1)<<7}return t[0]^=-e&225,t}var zt=t=>t>64*1024?8:t>1024?4:2,X=class{constructor(e,n){this.blockLen=S,this.outputLen=S,this.s0=0,this.s1=0,this.s2=0,this.s3=0,this.finished=!1,e=V(e),g(e,16);let o=D(e),r=o.getUint32(0,!1),s=o.getUint32(4,!1),c=o.getUint32(8,!1),f=o.getUint32(12,!1),i=[];for(let x=0;x<128;x++)i.push({s0:w(r),s1:w(s),s2:w(c),s3:w(f)}),{s0:r,s1:s,s2:c,s3:f}=Wt(r,s,c,f);let h=zt(n||1024);if(![1,2,4,8].includes(h))throw new Error("ghash: invalid window size, expected 2, 4 or 8");this.W=h;let a=128/h,d=this.windowSize=2**h,l=[];for(let x=0;x>>h-A-1&1))continue;let{s0:et,s1:It,s2:Ct,s3:Tt}=i[h*x+A];y^=et,m^=It,b^=Ct,O^=Tt}l.push({s0:y,s1:m,s2:b,s3:O})}this.t=l}_updateBlock(e,n,o,r){e^=this.s0,n^=this.s1,o^=this.s2,r^=this.s3;let{W:s,t:c,windowSize:f}=this,i=0,h=0,u=0,a=0,d=(1<>>8*p&255;for(let m=8/s-1;m>=0;m--){let b=y>>>s*m&d,{s0:O,s1:A,s2:M,s3:et}=c[l*f+b];i^=O,h^=A,u^=M,a^=et,l+=1}}this.s0=i,this.s1=h,this.s2=u,this.s3=a}update(e){W(this),e=V(e),g(e);let n=B(e),o=Math.floor(e.length/S),r=e.length%S;for(let s=0;st(r,o.length).update(V(o)).digest(),n=t(new Uint8Array(16),0);return e.outputLen=n.outputLen,e.blockLen=n.blockLen,e.create=(o,r)=>t(o,r),e}var it=Ht((t,e)=>new X(t,e)),jt=Ht((t,e)=>new st(t,e));var ht=16,Pt=4,$=new Uint8Array(ht),Rt=283;function at(t){return t<<1^Rt&-(t>>7)}function Ut(t,e){let n=0;for(;e>0;e>>=1)n^=t&-(e&1),t=at(t);return n}var Zt=(()=>{let t=new Uint8Array(256);for(let n=0,o=1;n<256;n++,o^=at(o))t[n]=o;let e=new Uint8Array(256);e[0]=99;for(let n=0;n<255;n++){let o=t[255-n];o|=o<<8,e[t[n]]=(o^o>>4^o>>5^o>>6^o>>7^99)&255}return L(t),e})();var Yt=t=>t<<24|t>>>8,ft=t=>t<<8|t>>>24;function qt(t,e){if(t.length!==256)throw new Error("Wrong sbox length");let n=new Uint32Array(256).map((h,u)=>e(t[u])),o=n.map(ft),r=o.map(ft),s=r.map(ft),c=new Uint32Array(256*256),f=new Uint32Array(256*256),i=new Uint16Array(256*256);for(let h=0;h<256;h++)for(let u=0;u<256;u++){let a=h*256+u;c[a]=n[h]^o[u],f[a]=r[h]^s[u],i[a]=t[h]<<8|t[u]}return{sbox:t,sbox2:i,T0:n,T1:o,T2:r,T3:s,T01:c,T23:f}}var St=qt(Zt,t=>Ut(t,3)<<24|t<<16|t<<8|Ut(t,2));var Jt=(()=>{let t=new Uint8Array(16);for(let e=0,n=1;e<16;e++,n=at(n))t[e]=n;return t})();function Xt(t){g(t);let e=t.length;if(![16,24,32].includes(e))throw new Error("aes: invalid key size, should be 16, 24 or 32, got "+e);let{sbox2:n}=St,o=[];N(t)||o.push(t=T(t));let r=B(t),s=r.length,c=i=>z(n,i,i,i,i),f=new Uint32Array(e+28);f.set(r);for(let i=s;i6&&i%s===4&&(h=c(h)),f[i]=f[i-s]^h}return L(...o),f}function Q(t,e,n,o,r,s){return t[n<<8&65280|o>>>8&255]^e[r>>>8&65280|s>>>24&255]}function z(t,e,n,o,r){return t[e&255|n&65280]|t[o>>>16&255|r>>>16&65280]<<16}function _t(t,e,n,o,r){let{sbox2:s,T01:c,T23:f}=St,i=0;e^=t[i++],n^=t[i++],o^=t[i++],r^=t[i++];let h=t.length/4-2;for(let x=0;x>>0,f.setUint32(u,d,e),{s0:l,s1:x,s2:p,s3:y}=_t(t,c[0],c[1],c[2],c[3]);let m=ht*Math.floor(i.length/Pt);if(m + + + + + + Glint Remote + + + + +
+
+
+ + +
+ Glint Remote + 选择一个终端 +
+
+
+ + 正在连接 + +
+
+ +
+ + + +
+
+
›_
+

从左侧选择一个终端

+

画面和输入会在浏览器与这台 Mac 上的 Glint 会话之间实时同步。

+
+
+
+ + + + + + + + +
+
+
+
+ + +
+
+
+ PROJECT +

新建项目

+
+ +
+ +
+ + +
+

目录必须已经存在;不会自动 git init 或 clone。

+
+
+ + +
+ AUTHENTICATION +

输入访问密钥

+

请在 Glint 设置 → Terminal → Web remote control 中单独复制访问密钥。

+ + +

+ +
+
+ + + + diff --git a/Glint/Resources/WebRemote/web-remote.css b/Glint/Resources/WebRemote/web-remote.css new file mode 100644 index 0000000..76e8c60 --- /dev/null +++ b/Glint/Resources/WebRemote/web-remote.css @@ -0,0 +1,421 @@ +@font-face { + font-family: "Glint Nerd Symbols"; + src: url("/symbols-nerd-font-mono.ttf") format("truetype"); + font-display: block; + font-style: normal; + font-weight: 400; +} + +:root { + color-scheme: dark; + --bg: #090a0f; + --panel: #11131b; + --panel-2: #171a24; + --terminal-bg: #0b0c11; + --text: #f4f5f8; + --muted: #8f95a8; + --faint: #626879; + --accent: #ff9a4d; + --accent-2: #ff6b5f; + --line: color-mix(in srgb, var(--text) 9%, transparent); + --overlay: color-mix(in srgb, var(--text) 4%, transparent); + --backdrop: rgba(4, 5, 8, .62); + --shadow: rgba(0, 0, 0, .42); + --ok: #53d68a; + --danger: #ff6b73; + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif; + color: var(--text); + background: var(--bg); +} + +:root[data-theme="light"] { + color-scheme: light; + --backdrop: rgba(38, 35, 32, .28); + --shadow: rgba(33, 29, 25, .18); + --ok: #148548; + --danger: #c73543; +} + +* { box-sizing: border-box; } + +html { + width: 100%; + height: 100%; + height: 100dvh; + overflow: hidden; +} + +body { + width: 100%; + height: var(--visual-viewport-height, 100dvh); + margin: 0; + overflow: hidden; + transform: translateY(var(--visual-viewport-offset-top, 0px)); + background: + radial-gradient(circle at 12% -10%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 31rem), + var(--bg); +} + +button, input { font: inherit; } +button { cursor: pointer; } + +.app-shell { + height: 100%; + min-height: 0; + display: grid; + grid-template-rows: 64px minmax(0, 1fr); +} + +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: max(10px, env(safe-area-inset-top)) 18px 10px; + border-bottom: 1px solid var(--line); + background: color-mix(in srgb, var(--bg) 82%, transparent); + backdrop-filter: blur(18px); +} + +.brand, .connection { display: flex; align-items: center; } +.brand { gap: 11px; min-width: 0; } +.brand > div { display: grid; gap: 2px; min-width: 0; } +.brand strong { font-size: 14px; letter-spacing: 0.01em; } +.brand span:not(.brand-mark) { + color: var(--muted); + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.brand-mark { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border-radius: 10px; + color: #170d08; + font-weight: 800; + background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 45%, white), var(--accent) 48%, var(--accent-2)); + box-shadow: 0 7px 24px color-mix(in srgb, var(--accent) 22%, transparent); +} +.brand-mark img { + width: 100%; + height: 100%; + border-radius: inherit; + object-fit: cover; +} +.brand .brand-mark > span { color: inherit; font-size: inherit; } +.brand-mark img[hidden] { display: none; } + +.connection { gap: 7px; color: var(--muted); font-size: 12px; } +.status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); } +.status-dot.connected { background: var(--ok); box-shadow: 0 0 12px rgba(83, 214, 138, 0.65); } +.status-dot.error { background: var(--danger); } + +.ghost-button, .icon-button { + border: 1px solid var(--line); + color: var(--muted); + background: var(--overlay); + border-radius: 7px; +} +.ghost-button { margin-left: 5px; padding: 5px 9px; } +.icon-button { + display: grid; + place-items: center; + width: 28px; + height: 28px; + padding: 0; + font-size: 16px; + line-height: 1; +} +.ghost-button:hover, .icon-button:hover { color: var(--text); border-color: color-mix(in srgb, var(--text) 15%, transparent); } +.mobile-sidebar-button, .sidebar-backdrop { display: none; } +.list-actions { display: flex; gap: 6px; } +.create-project-button { color: var(--accent); font-size: 19px; line-height: 1; } + +.workspace-layout { + min-height: 0; + display: grid; + grid-template-columns: minmax(270px, 320px) minmax(0, 1fr); +} + +.sidebar { + min-height: 0; + display: flex; + flex-direction: column; + border-right: 1px solid var(--line); + background: color-mix(in srgb, var(--panel) 78%, transparent); +} + +.section-heading { display: flex; align-items: center; justify-content: space-between; } +.section-heading h2 { margin: 3px 0 0; font-size: 14px; } +.eyebrow { color: var(--accent); font-size: 9px; font-weight: 800; letter-spacing: .15em; } +#create-form label, #auth-form label { display: block; margin-bottom: 6px; color: var(--muted); font-size: 11px; } +.path-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; } +input { + min-width: 0; + border: 1px solid var(--line); + border-radius: 8px; + outline: 0; + padding: 9px 10px; + color: var(--text); + background: color-mix(in srgb, var(--bg) 72%, transparent); + font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; + font-size: 11px; +} +input:focus { + border-color: color-mix(in srgb, var(--accent) 65%, transparent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 9%, transparent); +} + +.path-row button, #auth-form button { + border: 0; + border-radius: 8px; + padding: 0 13px; + color: #1b0d05; + font-weight: 700; + background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 55%, white), var(--accent)); +} + +.form-message { min-height: 15px; margin: 7px 0 0; color: var(--faint); font-size: 10px; line-height: 1.45; } +.form-message.error { color: var(--danger); } +.form-message.success { color: var(--ok); } + +.project-list-section { min-height: 0; display: flex; flex: 1; flex-direction: column; padding-top: 15px; } +.list-heading { padding: 0 16px 11px; } +.workspace-list { min-height: 0; flex: 1; overflow: auto; padding: 0 9px 18px; } +.workspace-group { margin-bottom: 8px; } +.workspace-pane-list { min-height: 0; } +.workspace-title { + display: flex; + align-items: center; + gap: 7px; + padding: 7px 8px 5px; + color: var(--muted); + font-size: 11px; + font-weight: 650; +} +.workspace-color { width: 7px; height: 7px; border-radius: 50%; } +.terminal-count { + min-width: 18px; + border-radius: 999px; + padding: 1px 5px; + color: var(--faint); + background: color-mix(in srgb, var(--text) 5%, transparent); + text-align: center; + font-size: 9px; +} +.workspace-title .archived { margin-left: auto; color: var(--faint); font-size: 9px; font-weight: 500; } +.workspace-new-terminal { + display: grid; + place-items: center; + width: 24px; + height: 24px; + margin-left: auto; + border: 1px solid var(--line); + border-radius: 7px; + padding: 0; + color: var(--accent); + background: color-mix(in srgb, var(--accent) 7%, transparent); + font-size: 17px; + line-height: 1; +} +.workspace-new-terminal:hover, +.workspace-new-terminal:focus-visible { + border-color: color-mix(in srgb, var(--accent) 35%, transparent); + background: color-mix(in srgb, var(--accent) 14%, transparent); +} +.pane-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 28px; + align-items: center; + gap: 2px; +} +.pane-button { + width: 100%; + display: grid; + grid-template-columns: 24px minmax(0, 1fr) auto; + align-items: center; + gap: 8px; + border: 1px solid transparent; + border-radius: 8px; + padding: 7px 8px; + color: var(--text); + text-align: left; + background: transparent; +} +.pane-close-terminal { + display: grid; + place-items: center; + width: 28px; + height: 28px; + border: 0; + border-radius: 7px; + padding: 0; + color: var(--faint); + background: transparent; + font-size: 17px; + line-height: 1; +} +.pane-close-terminal:hover, +.pane-close-terminal:focus-visible { + color: var(--danger); + background: color-mix(in srgb, var(--danger) 10%, transparent); +} +.pane-button:hover { background: var(--overlay); } +.pane-button.active { + border-color: color-mix(in srgb, var(--accent) 28%, transparent); + background: color-mix(in srgb, var(--accent) 9%, transparent); +} +.pane-icon { color: var(--accent); font-family: ui-monospace, monospace; font-size: 12px; } +.pane-copy { min-width: 0; display: grid; gap: 2px; } +.pane-copy strong, .pane-copy span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.pane-copy strong { font-size: 11px; font-weight: 600; } +.pane-copy span { color: var(--faint); font-family: ui-monospace, monospace; font-size: 9px; } +.agent-badge { color: var(--ok); font-size: 8px; text-transform: uppercase; } + +.terminal-panel { + min-width: 0; + min-height: 0; + position: relative; + display: grid; + grid-template-rows: minmax(0, 1fr) auto; + background: var(--terminal-bg); +} + +#terminal { min-width: 0; min-height: 0; padding: 12px 10px 4px; display: none; } +#terminal.visible { display: block; } +#terminal .xterm { height: 100%; } +#terminal .xterm-viewport { scrollbar-color: color-mix(in srgb, var(--text) 16%, transparent) transparent; } + +.empty-state { + position: absolute; + inset: 0 0 48px; + display: grid; + place-content: center; + justify-items: center; + padding: 30px; + text-align: center; + color: var(--muted); +} +.empty-state.hidden { display: none; } +.empty-icon { color: var(--accent); font: 700 24px/1 ui-monospace, monospace; } +.empty-state h1 { margin: 15px 0 7px; color: var(--text); font-size: 17px; } +.empty-state p { max-width: 430px; margin: 0; font-size: 12px; line-height: 1.6; } + +.quick-keys { + display: flex; + gap: 6px; + overflow-x: auto; + padding: 7px 10px calc(8px + env(safe-area-inset-bottom)); + border-top: 1px solid var(--line); + background: color-mix(in srgb, var(--terminal-bg) 96%, transparent); +} +.quick-keys button { + flex: 0 0 auto; + min-width: 42px; + border: 1px solid var(--line); + border-radius: 7px; + padding: 7px 9px; + color: var(--muted); + background: var(--overlay); + font: 600 10px ui-monospace, monospace; +} +.quick-keys button:active { color: var(--text); background: color-mix(in srgb, var(--accent) 13%, transparent); } + +dialog { + width: min(410px, calc(100vw - 32px)); + max-height: calc(var(--visual-viewport-height, 100dvh) - 24px); + overflow: auto; + border: 1px solid color-mix(in srgb, var(--text) 11%, transparent); + border-radius: 15px; + padding: 0; + color: var(--text); + background: var(--panel-2); + box-shadow: 0 30px 90px var(--shadow); +} +dialog::backdrop { background: var(--backdrop); backdrop-filter: blur(8px); } +#create-form, #auth-form { display: grid; padding: 25px; } +#create-form h1, #auth-form h1 { margin: 7px 0 8px; font-size: 20px; } +.dialog-heading { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; } +.dialog-heading h1 { margin-bottom: 0; } +#auth-form > p:not(.form-message) { margin: 0 0 18px; color: var(--muted); font-size: 12px; line-height: 1.55; } +#auth-form input { width: 100%; } +#auth-form button { height: 40px; margin-top: 13px; } + +@media (max-width: 760px), + (max-width: 900px) and (max-height: 520px) and (orientation: landscape) { + .app-shell { grid-template-rows: max(58px, calc(48px + env(safe-area-inset-top))) minmax(0, 1fr); } + .topbar { padding-inline: 12px; } + .connection #status-text { display: none; } + .mobile-sidebar-button { + display: grid; + place-items: center; + flex: 0 0 auto; + width: 32px; + height: 32px; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0; + color: var(--muted); + background: var(--overlay); + font-size: 17px; + } + .list-actions .mobile-sidebar-button { width: 28px; height: 28px; } + .workspace-layout { display: block; position: relative; } + .sidebar { + position: fixed; + z-index: 30; + inset: 0 auto 0 0; + width: min(340px, 88vw); + overflow: hidden; + padding-top: env(safe-area-inset-top); + border-right: 1px solid var(--line); + background: var(--panel); + box-shadow: 18px 0 50px var(--shadow); + transform: translateX(-105%); + transition: transform 180ms ease; + } + .sidebar.open { transform: translateX(0); } + .sidebar-backdrop { + position: fixed; + z-index: 20; + inset: 0; + width: 100%; + height: 100%; + border: 0; + padding: 0; + background: var(--backdrop); + opacity: 0; + pointer-events: none; + transition: opacity 180ms ease; + } + .sidebar-backdrop.open { display: block; opacity: 1; pointer-events: auto; } + input { font-size: 16px; } + .project-list-section { padding-top: 9px; } + .list-heading { padding: 0 12px 6px; } + .workspace-list { padding-inline: 6px; } + .workspace-group { display: block; width: auto; } + .workspace-pane-list { overflow: visible; } + #workspace-list { white-space: normal; overflow: auto; } + .terminal-panel { height: 100%; } + #terminal { padding-top: 7px; } +} + +@media (max-width: 480px) { + .brand-mark { width: 30px; height: 30px; border-radius: 9px; } + .brand strong { font-size: 13px; } + #active-label { max-width: 130px; } + .ghost-button { margin-left: 1px; padding-inline: 7px; } + .brand-mark { display: none; } + .form-message { margin-top: 4px; } + .quick-keys { scroll-snap-type: x proximity; } + .quick-keys button { scroll-snap-align: start; } + #auth-form { padding: 20px; } +} + +@media (max-width: 900px) and (max-height: 520px) and (orientation: landscape) { + .app-shell { grid-template-rows: 50px minmax(0, 1fr); } + .project-list-section { padding-top: 6px; } +} diff --git a/Glint/Resources/WebRemote/web-remote.js b/Glint/Resources/WebRemote/web-remote.js new file mode 100644 index 0000000..f5da93d --- /dev/null +++ b/Glint/Resources/WebRemote/web-remote.js @@ -0,0 +1,900 @@ +import { Terminal } from "/xterm.mjs"; +import { FitAddon } from "/addon-fit.mjs"; +import { + hmacSha256, + hkdfExtractExpand, + hkdfExpand, + aesGcmSeal, + aesGcmOpen, +} from "/crypto.mjs"; + +// Crypto labels — must match the Swift server's WebRemoteCrypto byte-for-byte. +const PROOF_LABEL = "glint-webremote/v1/proof"; +const SESSION_INFO = "glint-webremote/v1/session"; +const C2S_INFO = "c2s"; +const S2C_INFO = "s2c"; + +const textEncoder = new TextEncoder(); +const textDecoder = new TextDecoder(); + +function hexToBytes(hex) { + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i += 1) { + bytes[i] = Number.parseInt(hex.substr(i * 2, 2), 16); + } + return bytes; +} + +function concatBytes(...parts) { + let length = 0; + for (const part of parts) length += part.length; + const out = new Uint8Array(length); + let offset = 0; + for (const part of parts) { + out.set(part, offset); + offset += part.length; + } + return out; +} + +// 12-byte GCM nonce: 4 zero bytes + 8-byte big-endian counter (matches Swift). +function nonceFor(counter) { + const nonce = new Uint8Array(12); + new DataView(nonce.buffer).setBigUint64(4, BigInt(counter), false); + return nonce; +} + +function counterFromNonce(nonce) { + return Number(new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength) + .getBigUint64(4, false)); +} + +const language = (navigator.languages?.[0] || navigator.language || "en") + .toLowerCase() + .startsWith("zh") ? "zh" : "en"; +const translations = { + zh: { + access_key: "访问密钥", + access_key_help: "请在 Glint 设置 → Terminal → Web remote control 中单独复制访问密钥。", + bad_request: "请求格式错误", + choose_terminal_heading: "从左侧选择一个终端", + close_new_project: "关闭新建项目", + close_sidebar: "关闭项目与终端", + close_terminal: "关闭终端", + close_terminal_aria: "关闭 {name}", + close_terminal_confirm: "“{name}”中仍有程序运行。确定要关闭并终止它吗?", + connect_glint: "连接 Glint", + connected: "已连接", + connecting: "正在连接", + create_terminal: "正在新建终端", + disconnected: "连接已断开", + enter_access_key: "输入访问密钥", + existing_directory_hint: "目录必须已经存在;不会自动 git init 或 clone。", + invalid_project_path: "目录不存在,或不是这台 Mac 上可访问的文件夹。", + loading_terminal: "正在载入终端", + last_terminal: "每个 Workspace 至少需要保留一个终端", + mac_directory: "这台 Mac 上的目录", + new_project: "新建项目", + open: "打开", + open_sidebar: "打开项目与终端", + opening_project: "正在打开项目…", + operation_failed: "操作失败:{code}", + project_opened: "项目已打开。", + projects_and_terminals: "项目与终端", + quick_keys: "快捷键", + reconnect: "重连", + refresh: "刷新", + remote_terminal: "远程终端", + select_terminal: "选择一个终端", + selection_in_progress: "正在切换终端,请稍候", + sync_description: "画面和输入会在浏览器与这台 Mac 上的 Glint 会话之间实时同步。", + syncing_terminal: "正在同步终端画面…", + terminal_count: "{count} 个终端", + terminal_closed: "终端已关闭", + terminal_created: "已新建终端", + terminal_not_ready: "终端尚未准备好", + terminal_ready: "终端已就绪", + unable_connect: "无法连接", + unauthorized: "访问密钥无效或已经失效。", + unknown_command: "不支持的操作", + unknown_pane: "终端已经不存在", + unknown_workspace: "Workspace 已经不存在", + waiting_terminal: "等待终端画面", + workspace_archived: "归档的 Workspace 不能新建终端", + workspace_new_terminal: "在此 Workspace 中新建终端", + workspace_new_terminal_aria: "在 {name} 中新建终端", + }, + en: { + access_key: "Access key", + access_key_help: "Copy the access key from Glint Settings → Terminal → Web remote control.", + bad_request: "Invalid request", + choose_terminal_heading: "Choose a terminal from the sidebar", + close_new_project: "Close new project", + close_sidebar: "Close projects and terminals", + close_terminal: "Close terminal", + close_terminal_aria: "Close {name}", + close_terminal_confirm: "A process is still running in “{name}”. Close the terminal and terminate it?", + connect_glint: "Connect to Glint", + connected: "Connected", + connecting: "Connecting", + create_terminal: "Creating terminal", + disconnected: "Disconnected", + enter_access_key: "Enter access key", + existing_directory_hint: "The directory must already exist; Glint will not run git init or clone.", + invalid_project_path: "The directory does not exist or is not accessible on this Mac.", + loading_terminal: "Loading terminal", + last_terminal: "Each workspace must keep at least one terminal", + mac_directory: "Directory on this Mac", + new_project: "New project", + open: "Open", + open_sidebar: "Open projects and terminals", + opening_project: "Opening project…", + operation_failed: "Operation failed: {code}", + project_opened: "Project opened.", + projects_and_terminals: "Projects and terminals", + quick_keys: "Quick keys", + reconnect: "Reconnect", + refresh: "Refresh", + remote_terminal: "Remote terminal", + select_terminal: "Select a terminal", + selection_in_progress: "Switching terminals, please wait", + sync_description: "The browser stays in sync with this Mac's live Glint session.", + syncing_terminal: "Syncing terminal…", + terminal_count: "{count} terminal(s)", + terminal_closed: "Terminal closed", + terminal_created: "Terminal created", + terminal_not_ready: "Terminal is not ready", + terminal_ready: "Terminal ready", + unable_connect: "Unable to connect", + unauthorized: "The access key is invalid or has expired.", + unknown_command: "Unsupported operation", + unknown_pane: "The terminal no longer exists", + unknown_workspace: "The workspace no longer exists", + waiting_terminal: "Waiting for terminal", + workspace_archived: "Cannot create a terminal in an archived workspace", + workspace_new_terminal: "Create a terminal in this workspace", + workspace_new_terminal_aria: "Create a terminal in {name}", + }, +}; + +function t(key, values = {}) { + let value = translations[language][key] || key; + for (const [name, replacement] of Object.entries(values)) { + value = value.replace(`{${name}}`, String(replacement)); + } + return value; +} + +function localizeDocument() { + document.documentElement.lang = language === "zh" ? "zh-CN" : "en"; + document.querySelectorAll("[data-i18n]").forEach(element => { + element.textContent = t(element.dataset.i18n); + }); + document.querySelectorAll("[data-i18n-aria]").forEach(element => { + element.setAttribute("aria-label", t(element.dataset.i18nAria)); + }); +} + +const elements = { + activeLabel: document.querySelector("#active-label"), + authDialog: document.querySelector("#auth-dialog"), + authError: document.querySelector("#auth-error"), + authForm: document.querySelector("#auth-form"), + brandFallback: document.querySelector("#brand-fallback"), + brandIcon: document.querySelector("#brand-icon"), + createClose: document.querySelector("#create-close"), + createDialog: document.querySelector("#create-dialog"), + createForm: document.querySelector("#create-form"), + createMessage: document.querySelector("#create-message"), + createProject: document.querySelector("#create-project"), + emptyState: document.querySelector("#empty-state"), + projectPath: document.querySelector("#project-path"), + reconnect: document.querySelector("#reconnect"), + refresh: document.querySelector("#refresh"), + sidebar: document.querySelector("#sidebar"), + sidebarBackdrop: document.querySelector("#sidebar-backdrop"), + sidebarClose: document.querySelector("#sidebar-close"), + sidebarToggle: document.querySelector("#sidebar-toggle"), + statusDot: document.querySelector("#status-dot"), + statusText: document.querySelector("#status-text"), + terminal: document.querySelector("#terminal"), + tokenInput: document.querySelector("#token-input"), + workspaceList: document.querySelector("#workspace-list"), +}; +localizeDocument(); + +const defaultTerminalTheme = { + background: "#0b0c11", + foreground: "#e8e9ee", + cursor: "#ff9a4d", + cursorAccent: "#0b0c11", + selectionBackground: "#5e5ce6", + selectionForeground: "#e8e9ee", + black: "#171923", + red: "#ff6b73", + green: "#53d68a", + yellow: "#f3c969", + blue: "#76a9ff", + magenta: "#c792ea", + cyan: "#63d6d1", + white: "#e8e9ee", + brightBlack: "#626879", + brightRed: "#ff8b91", + brightGreen: "#79e5a4", + brightYellow: "#ffe08a", + brightBlue: "#9fc2ff", + brightMagenta: "#d8a8f2", + brightCyan: "#8be8e3", + brightWhite: "#ffffff", +}; + +const terminal = new Terminal({ + allowProposedApi: false, + convertEol: false, + cursorBlink: true, + cursorStyle: "block", + fontFamily: '"Maple Mono NF CN", "SFMono-Regular", Menlo, Monaco, "Noto Sans Mono CJK SC", "Glint Nerd Symbols", monospace', + fontSize: matchMedia("(max-width: 480px)").matches ? 12 : 13, + lineHeight: 1.16, + scrollback: 5000, + theme: defaultTerminalTheme, +}); +const fitAddon = new FitAddon(); +terminal.loadAddon(fitAddon); +terminal.open(elements.terminal); +document.fonts?.load('13px "Glint Nerd Symbols"').then(() => { + terminal.refresh(0, terminal.rows - 1); + fitTerminal(); +}); + +let socket; +let reconnectTimer; +let reconnectDelay = 500; +let authenticated = false; +let selectedPane = sessionStorage.getItem("glint-selected-pane") || ""; +let lastState; +let token = loadToken(); +// Challenge-response + encryption session. `pendingChallenge` is set when the +// server sends auth-challenge; once both `token` and `pendingChallenge` are +// present, `tryAuthenticate()` derives the keys and sends the proof. The token +// never leaves the browser — only its HMAC over the challenge does. `encrypted` +// flips true on the first binary frame the server sends back. +let pendingChallenge = null; +let c2sKey = null; +let s2cKey = null; +let sendCounter = 0; +let receiveCounter = 0; +let encrypted = false; +let paneRetryTimer; +let paneRetryCount = 0; +const paneRetryLimit = 40; +let controllingPane = ""; +let resizeTimer; +let lastSentTerminalSize = ""; +let appliedBrandSignature = ""; +let appliedThemeSignature = ""; +const mobileSidebarLayout = matchMedia( + "(max-width: 760px), (max-width: 900px) and (max-height: 520px) and (orientation: landscape)" +); + +function loadToken() { + const fragment = new URLSearchParams(location.hash.slice(1)); + const fromLink = fragment.get("token"); + if (fromLink) { + sessionStorage.setItem("glint-session-token", fromLink); + history.replaceState(null, "", `${location.pathname}${location.search}`); + return fromLink; + } + return sessionStorage.getItem("glint-session-token") || ""; +} + +function websocketURL() { + const scheme = location.protocol === "https:" ? "wss:" : "ws:"; + const httpPort = Number(location.port || 43871); + return `${scheme}//${location.hostname}:${httpPort + 1}/control`; +} + +function connect() { + clearTimeout(reconnectTimer); + if (socket) { + socket.close(); + } + authenticated = false; + controllingPane = ""; + resetSession(); + setStatus("connecting", t("connecting")); + socket = new WebSocket(websocketURL()); + const currentSocket = socket; + socket.binaryType = "arraybuffer"; + socket.addEventListener("open", () => { + reconnectDelay = 500; + // The server issues an auth-challenge as soon as the socket opens; we wait + // for it rather than sending the token ourselves. + if (!token) showAuth(); + }); + socket.addEventListener("message", event => { + const data = event.data; + if (typeof data === "string") { + handleMessage(data); // plaintext: auth-challenge / handshake error + } else { + onEncryptedFrame(data); // binary: encrypted frame + } + }); + socket.addEventListener("close", () => { + if (socket !== currentSocket) return; + authenticated = false; + controllingPane = ""; + setStatus("error", t("disconnected")); + reconnectTimer = setTimeout(connect, reconnectDelay); + reconnectDelay = Math.min(reconnectDelay * 1.8, 8000); + }); + socket.addEventListener("error", () => setStatus("error", t("unable_connect"))); +} + +function resetSession() { + pendingChallenge = null; + c2sKey = null; + s2cKey = null; + sendCounter = 0; + receiveCounter = 0; + encrypted = false; +} + +/// Derive the session keys from the access key + server challenge, returning the +/// base64 HMAC proof to send. Sets c2s/s2c keys and zeroes the counters. +function deriveSession(accessKey, challengeB64) { + const tokenKey = hexToBytes(accessKey); + const challenge = decodeBase64(challengeB64); + const proof = hmacSha256( + tokenKey, + concatBytes(textEncoder.encode(PROOF_LABEL), challenge) + ); + const sessionKey = hkdfExtractExpand( + tokenKey, + challenge, + textEncoder.encode(SESSION_INFO), + 32 + ); + c2sKey = hkdfExpand(sessionKey, textEncoder.encode(C2S_INFO), 32); + s2cKey = hkdfExpand(sessionKey, textEncoder.encode(S2C_INFO), 32); + sendCounter = 0; + receiveCounter = 0; + encrypted = false; + return encodeBase64(proof); +} + +/// Send the proof once both the access key and the challenge are known. +function tryAuthenticate() { + if (!token || !pendingChallenge) return; + if (!socket || socket.readyState !== WebSocket.OPEN) return; + const proof = deriveSession(token, pendingChallenge); + // The proof is the last plaintext frame; encryption turns on only when the + // server's first binary reply arrives. + send({ type: "authenticate", proof }); +} + +function encryptFrame(json) { + const nonce = nonceFor(sendCounter); + const body = aesGcmSeal(c2sKey, nonce, textEncoder.encode(json)); + const frame = new Uint8Array(nonce.length + body.length); + frame.set(nonce, 0); + frame.set(body, nonce.length); + sendCounter += 1; + return frame; +} + +function decryptFrame(frame) { + if (!s2cKey || frame.length <= 12 + 16) return null; + const nonce = frame.subarray(0, 12); + const body = frame.subarray(12); + const counter = counterFromNonce(nonce); + if (counter < receiveCounter) return null; // replay guard + try { + const plain = aesGcmOpen(s2cKey, nonce, body); + receiveCounter = counter + 1; + return textDecoder.decode(plain); + } catch { + return null; + } +} + +function onEncryptedFrame(buffer) { + const json = decryptFrame(new Uint8Array(buffer)); + if (json === null) { + // Tamper / replay / key mismatch — drop the socket and let it reconnect. + socket?.close(); + return; + } + encrypted = true; + handleMessage(json); +} + +function send(message) { + if (!socket || socket.readyState !== WebSocket.OPEN) return; + const json = JSON.stringify(message); + if (encrypted && c2sKey) { + socket.send(encryptFrame(json)); + } else { + socket.send(json); + } +} + +function handleMessage(raw) { + let message; + try { + message = JSON.parse(raw); + } catch { + return; + } + switch (message.type) { + case "auth-challenge": + pendingChallenge = message.challenge; + tryAuthenticate(); + break; + case "authenticated": + authenticated = true; + setStatus("connected", t("connected")); + if (elements.authDialog.open) elements.authDialog.close(); + elements.authError.textContent = ""; + break; + case "state": + lastState = message; + applyBrand(message.brand); + applyTheme(message.theme); + renderState(message); + chooseInitialPane(message); + break; + case "snapshot": + if (message.pane !== selectedPane) return; + clearTimeout(paneRetryTimer); + paneRetryCount = 0; + terminal.reset(); + terminal.write(decodeBase64(message.data), () => { + controllingPane = message.pane; + fitTerminal(); + terminal.focus(); + }); + elements.emptyState.classList.add("hidden"); + elements.terminal.classList.add("visible"); + break; + case "output": + if (message.pane === selectedPane) terminal.write(decodeBase64(message.data)); + break; + case "projectCreated": + elements.projectPath.value = ""; + elements.createDialog.close(); + selectedPane = ""; + controllingPane = ""; + sessionStorage.removeItem("glint-selected-pane"); + setSidebarOpen(false); + setStatus("connected", t("project_opened")); + break; + case "terminalCreated": + setStatus("connected", t("terminal_created")); + selectPane(message.pane); + break; + case "terminalCloseConfirmation": { + const pane = lastState?.workspaces + .flatMap(workspace => workspace.panes || []) + .find(item => item.id === message.pane); + const name = pane?.title || "Terminal"; + if (window.confirm(t("close_terminal_confirm", { name }))) { + send({ type: "closeTerminal", pane: message.pane, confirmed: true }); + } + break; + } + case "terminalClosed": + if (message.pane === selectedPane) { + selectedPane = ""; + controllingPane = ""; + clearTimeout(paneRetryTimer); + paneRetryCount = 0; + sessionStorage.removeItem("glint-selected-pane"); + terminal.reset(); + elements.terminal.classList.remove("visible"); + elements.emptyState.classList.remove("hidden"); + elements.activeLabel.textContent = t("select_terminal"); + } + setStatus("connected", t("terminal_closed")); + break; + case "error": + handleError(message.code); + break; + } +} + +function applyBrand(brand) { + if (!brand || typeof brand.preset !== "string" || + typeof brand.dataURL !== "string" || + !brand.dataURL.startsWith("data:image/png;base64,")) return; + if (brand.preset === appliedBrandSignature) return; + appliedBrandSignature = brand.preset; + elements.brandIcon.src = brand.dataURL; + elements.brandIcon.hidden = false; + elements.brandFallback.hidden = true; +} + +function applyTheme(theme) { + if (!theme || !Array.isArray(theme.palette) || theme.palette.length !== 16) return; + const colors = [ + theme.background, + theme.foreground, + theme.cursor, + theme.selectionBackground, + theme.selectionForeground, + ...theme.palette, + ]; + if (!colors.every(color => /^#[0-9a-f]{6}$/i.test(color))) return; + + const chrome = theme.chrome || {}; + const signature = [ + theme.id, + theme.dark, + ...colors, + chrome.window, + chrome.pane, + chrome.sidebar, + chrome.text1, + chrome.text3, + chrome.text4, + chrome.accent, + ].join("|"); + if (signature === appliedThemeSignature) return; + appliedThemeSignature = signature; + + const root = document.documentElement; + root.dataset.theme = theme.dark ? "dark" : "light"; + root.style.setProperty("--bg", chrome.window || theme.background); + root.style.setProperty("--panel", chrome.sidebar || theme.background); + root.style.setProperty("--panel-2", chrome.pane || theme.background); + root.style.setProperty("--terminal-bg", theme.background); + root.style.setProperty("--text", chrome.text1 || theme.foreground); + root.style.setProperty("--muted", chrome.text3 || theme.foreground); + root.style.setProperty("--faint", chrome.text4 || theme.foreground); + root.style.setProperty("--accent", chrome.accent || theme.cursor); + root.style.setProperty("--accent-2", theme.palette[1]); + + terminal.options.theme = { + background: theme.background, + foreground: theme.foreground, + cursor: theme.cursor, + cursorAccent: theme.background, + selectionBackground: theme.selectionBackground, + selectionForeground: theme.selectionForeground, + black: theme.palette[0], + red: theme.palette[1], + green: theme.palette[2], + yellow: theme.palette[3], + blue: theme.palette[4], + magenta: theme.palette[5], + cyan: theme.palette[6], + white: theme.palette[7], + brightBlack: theme.palette[8], + brightRed: theme.palette[9], + brightGreen: theme.palette[10], + brightYellow: theme.palette[11], + brightBlue: theme.palette[12], + brightMagenta: theme.palette[13], + brightCyan: theme.palette[14], + brightWhite: theme.palette[15], + }; +} + +function handleError(code) { + if (code === "unauthorized") { + authenticated = false; + token = ""; + encrypted = false; // proof failed; we're still in the handshake + sessionStorage.removeItem("glint-session-token"); + elements.authError.textContent = t("unauthorized"); + showAuth(); + return; + } + if (code === "pane-not-ready" && selectedPane) { + paneRetryCount += 1; + if (paneRetryCount > paneRetryLimit) { + clearTimeout(paneRetryTimer); + setStatus("error", t("terminal_not_ready")); + return; + } + clearTimeout(paneRetryTimer); + paneRetryTimer = setTimeout(sendPaneSelection, 250); + return; + } + if (code === "invalid-project-path") { + setCreateMessage(t("invalid_project_path"), "error"); + return; + } + setStatus("error", errorLabel(code)); +} + +function errorLabel(code) { + const labels = { + "bad-request": t("bad_request"), + "unknown-pane": t("unknown_pane"), + "unknown-workspace": t("unknown_workspace"), + "workspace-archived": t("workspace_archived"), + "last-terminal": t("last_terminal"), + "terminal-not-ready": t("terminal_not_ready"), + "selection-in-progress": t("selection_in_progress"), + "unknown-command": t("unknown_command"), + }; + return labels[code] || t("operation_failed", { code }); +} + +function chooseInitialPane(state) { + const panes = state.workspaces.flatMap(workspace => workspace.panes || []); + const remembered = panes.find(pane => pane.id === selectedPane); + const preferred = remembered || panes.find(pane => pane.selected) || panes[0]; + if (preferred && preferred.id !== selectedPane) { + selectPane(preferred.id); + } else if (preferred && controllingPane !== preferred.id) { + selectPane(preferred.id); + } +} + +function renderState(state) { + elements.workspaceList.replaceChildren(); + for (const workspace of state.workspaces) { + const panes = workspace.panes || []; + const group = document.createElement("div"); + group.className = "workspace-group"; + + const title = document.createElement("div"); + title.className = "workspace-title"; + const color = document.createElement("span"); + color.className = "workspace-color"; + color.style.backgroundColor = `#${workspace.accent}`; + const name = document.createElement("span"); + name.textContent = workspace.name; + const count = document.createElement("span"); + count.className = "terminal-count"; + count.textContent = String(panes.length); + count.title = t("terminal_count", { count: panes.length }); + title.append(color, name, count); + if (workspace.archived) { + const archived = document.createElement("span"); + archived.className = "archived"; + archived.textContent = "ARCHIVED"; + title.append(archived); + } else { + const addTerminal = document.createElement("button"); + addTerminal.type = "button"; + addTerminal.className = "workspace-new-terminal"; + addTerminal.textContent = "+"; + addTerminal.title = t("workspace_new_terminal"); + addTerminal.setAttribute("aria-label", t("workspace_new_terminal_aria", { name: workspace.name })); + addTerminal.addEventListener("click", () => { + setStatus("connecting", t("create_terminal")); + send({ type: "createTerminal", workspace: workspace.id }); + }); + title.append(addTerminal); + } + group.append(title); + + const paneList = document.createElement("div"); + paneList.className = "workspace-pane-list"; + for (const pane of panes) { + const row = document.createElement("div"); + row.className = "pane-row"; + const button = document.createElement("button"); + button.type = "button"; + button.className = `pane-button${pane.id === selectedPane ? " active" : ""}`; + button.addEventListener("click", () => selectPane(pane.id)); + + const icon = document.createElement("span"); + icon.className = "pane-icon"; + icon.textContent = ">_"; + const copy = document.createElement("span"); + copy.className = "pane-copy"; + const paneTitle = document.createElement("strong"); + paneTitle.textContent = pane.title || "Terminal"; + const cwd = document.createElement("span"); + cwd.textContent = pane.cwd || (pane.ready ? t("terminal_ready") : t("waiting_terminal")); + copy.append(paneTitle, cwd); + button.append(icon, copy); + if (pane.agent) { + const badge = document.createElement("span"); + badge.className = "agent-badge"; + badge.textContent = pane.agent; + button.append(badge); + } + const closeButton = document.createElement("button"); + closeButton.type = "button"; + closeButton.className = "pane-close-terminal"; + closeButton.textContent = "×"; + closeButton.title = t("close_terminal"); + closeButton.setAttribute( + "aria-label", + t("close_terminal_aria", { name: pane.title || "Terminal" }) + ); + closeButton.addEventListener("click", () => { + send({ type: "closeTerminal", pane: pane.id, confirmed: false }); + }); + row.append(button, closeButton); + paneList.append(row); + } + group.append(paneList); + elements.workspaceList.append(group); + } +} + +function selectPane(pane) { + selectedPane = pane; + controllingPane = ""; + clearTimeout(paneRetryTimer); + paneRetryCount = 0; + sessionStorage.setItem("glint-selected-pane", pane); + if (lastState) { + const workspace = lastState.workspaces.find(item => item.panes?.some(value => value.id === pane)); + const selected = workspace?.panes.find(value => value.id === pane); + elements.activeLabel.textContent = workspace + ? `${workspace.name} · ${selected?.title || "Terminal"}` + : t("loading_terminal"); + renderState(lastState); + } + elements.emptyState.classList.add("hidden"); + elements.terminal.classList.add("visible"); + terminal.reset(); + terminal.write(`\x1b[2m${t("syncing_terminal")}\x1b[0m`); + sendPaneSelection(); + if (mobileSidebarLayout.matches) setSidebarOpen(false); +} + +function sendPaneSelection() { + if (!selectedPane) return; + fitTerminal(false); + const size = terminalSize(); + lastSentTerminalSize = `${size.columns}x${size.rows}`; + send({ type: "select", pane: selectedPane, columns: size.columns, rows: size.rows }); +} + +function sendInputBytes(bytes) { + if (!authenticated || !selectedPane || !bytes.length) return; + send({ type: "input", pane: selectedPane, data: encodeBase64(bytes) }); +} + +terminal.onData(data => sendInputBytes(new TextEncoder().encode(data))); +terminal.onBinary(data => { + const bytes = Uint8Array.from(data, character => character.charCodeAt(0) & 0xff); + sendInputBytes(bytes); +}); + +elements.createProject.addEventListener("click", () => { + setCreateMessage(t("existing_directory_hint"), ""); + elements.createDialog.showModal(); + requestAnimationFrame(() => elements.projectPath.focus()); +}); +elements.createClose.addEventListener("click", () => elements.createDialog.close()); + +elements.createForm.addEventListener("submit", event => { + event.preventDefault(); + const path = elements.projectPath.value.trim(); + if (!path) return; + setCreateMessage(t("opening_project"), ""); + send({ type: "createProject", path }); +}); + +elements.authForm.addEventListener("submit", event => { + event.preventDefault(); + token = elements.tokenInput.value.trim(); + if (!token) return; + sessionStorage.setItem("glint-session-token", token); + elements.authError.textContent = ""; + if (socket?.readyState === WebSocket.OPEN) { + // Challenge may already be in hand; otherwise tryAuthenticate waits for it. + tryAuthenticate(); + } else { + connect(); + } +}); + +elements.reconnect.addEventListener("click", connect); +elements.refresh.addEventListener("click", () => send({ type: "list" })); +elements.sidebarToggle.addEventListener("click", () => setSidebarOpen(true)); +elements.sidebarClose.addEventListener("click", () => setSidebarOpen(false)); +elements.sidebarBackdrop.addEventListener("click", () => setSidebarOpen(false)); +mobileSidebarLayout.addEventListener("change", event => { + if (!event.matches) setSidebarOpen(false); +}); +document.addEventListener("keydown", event => { + if (event.key === "Escape" && elements.sidebar.classList.contains("open")) { + setSidebarOpen(false); + } +}); +setSidebarOpen(false); +document.querySelectorAll("[data-input]").forEach(button => { + button.addEventListener("pointerdown", event => event.preventDefault()); + button.addEventListener("click", () => { + terminal.focus(); + const hex = button.dataset.input; + const bytes = new Uint8Array(hex.match(/.{2}/g).map(value => Number.parseInt(value, 16))); + sendInputBytes(bytes); + requestAnimationFrame(() => terminal.focus()); + }); +}); + +function showAuth() { + if (!elements.authDialog.open) elements.authDialog.showModal(); + elements.tokenInput.value = ""; + setTimeout(() => elements.tokenInput.focus(), 0); +} + +function setStatus(state, text) { + elements.statusDot.className = `status-dot ${state}`; + elements.statusText.textContent = text; +} + +function setCreateMessage(text, tone) { + elements.createMessage.className = `form-message ${tone}`.trim(); + elements.createMessage.textContent = text; +} + +function setSidebarOpen(open) { + elements.sidebar.classList.toggle("open", open); + elements.sidebarBackdrop.classList.toggle("open", open); + elements.sidebarToggle.setAttribute("aria-expanded", String(open)); + const hidden = mobileSidebarLayout.matches && !open; + elements.sidebar.inert = hidden; + elements.sidebar.setAttribute("aria-hidden", String(hidden)); +} + +function terminalSize() { + return { columns: terminal.cols, rows: terminal.rows }; +} + +function fitTerminal(notifyRemote = true) { + if (!elements.terminal.classList.contains("visible")) return; + try { fitAddon.fit(); } catch { /* Layout can be between mobile rotations. */ } + if (notifyRemote) scheduleTerminalResize(); +} + +function scheduleTerminalResize() { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + if (!authenticated || controllingPane !== selectedPane) return; + const size = terminalSize(); + const key = `${size.columns}x${size.rows}`; + if (key === lastSentTerminalSize) return; + lastSentTerminalSize = key; + send({ + type: "resize", + pane: selectedPane, + columns: size.columns, + rows: size.rows, + }); + }, 120); +} + +function syncVisualViewport() { + const viewport = window.visualViewport; + const height = Math.max(1, Math.round(viewport?.height || window.innerHeight)); + const offsetTop = Math.max(0, Math.round(viewport?.offsetTop || 0)); + document.documentElement.style.setProperty("--visual-viewport-height", `${height}px`); + document.documentElement.style.setProperty("--visual-viewport-offset-top", `${offsetTop}px`); +} + +function decodeBase64(value) { + const binary = atob(value); + return Uint8Array.from(binary, character => character.charCodeAt(0)); +} + +function encodeBase64(bytes) { + let binary = ""; + const chunkSize = 8192; + for (let offset = 0; offset < bytes.length; offset += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize)); + } + return btoa(binary); +} + +window.addEventListener("resize", syncVisualViewport); +window.visualViewport?.addEventListener("resize", syncVisualViewport); +window.visualViewport?.addEventListener("scroll", syncVisualViewport); +new ResizeObserver(fitTerminal).observe(elements.terminal); +syncVisualViewport(); +setInterval(() => { + if (authenticated) send({ type: "list" }); +}, 3000); + +connect(); diff --git a/Glint/Resources/WebRemote/xterm-LICENSE.txt b/Glint/Resources/WebRemote/xterm-LICENSE.txt new file mode 100644 index 0000000..4472336 --- /dev/null +++ b/Glint/Resources/WebRemote/xterm-LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js) +Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com) +Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Glint/Resources/WebRemote/xterm.css b/Glint/Resources/WebRemote/xterm.css new file mode 100644 index 0000000..819654e --- /dev/null +++ b/Glint/Resources/WebRemote/xterm.css @@ -0,0 +1,285 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * @license MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */ + +/** + * Default styles for xterm.js + */ + +.xterm { + cursor: text; + position: relative; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} + +.xterm.focus, +.xterm:focus { + outline: none; +} + +.xterm .xterm-helpers { + position: absolute; + top: 0; + /** + * The z-index of the helpers must be higher than the canvases in order for + * IMEs to appear on top. + */ + z-index: 5; +} + +.xterm .xterm-helper-textarea { + padding: 0; + border: 0; + margin: 0; + /* Move textarea out of the screen to the far left, so that the cursor is not visible */ + position: absolute; + opacity: 0; + left: -9999em; + top: 0; + width: 0; + height: 0; + z-index: -5; + /** Prevent wrapping so the IME appears against the textarea at the correct position */ + white-space: nowrap; + overflow: hidden; + resize: none; +} + +.xterm .composition-view { + /* TODO: Composition position got messed up somewhere */ + background: #000; + color: #FFF; + display: none; + position: absolute; + white-space: nowrap; + z-index: 1; +} + +.xterm .composition-view.active { + display: block; +} + +.xterm .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; + overflow-y: scroll; + cursor: default; + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; +} + +.xterm .xterm-screen { + position: relative; +} + +.xterm .xterm-screen canvas { + position: absolute; + left: 0; + top: 0; +} + +.xterm-char-measure-element { + display: inline-block; + visibility: hidden; + position: absolute; + top: 0; + left: -9999em; + line-height: normal; +} + +.xterm.enable-mouse-events { + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + cursor: default; +} + +.xterm.xterm-cursor-pointer, +.xterm .xterm-cursor-pointer { + cursor: pointer; +} + +.xterm.column-select.focus { + /* Column selection mode */ + cursor: crosshair; +} + +.xterm .xterm-accessibility:not(.debug), +.xterm .xterm-message { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 10; + color: transparent; + pointer-events: none; +} + +.xterm .xterm-accessibility-tree:not(.debug) *::selection { + color: transparent; +} + +.xterm .xterm-accessibility-tree { + font-family: monospace; + user-select: text; + white-space: pre; +} + +.xterm .xterm-accessibility-tree > div { + transform-origin: left; + width: fit-content; +} + +.xterm .live-region { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +.xterm-dim { + /* Dim should not apply to background, so the opacity of the foreground color is applied + * explicitly in the generated class and reset to 1 here */ + opacity: 1 !important; +} + +.xterm-underline-1 { text-decoration: underline; } +.xterm-underline-2 { text-decoration: double underline; } +.xterm-underline-3 { text-decoration: wavy underline; } +.xterm-underline-4 { text-decoration: dotted underline; } +.xterm-underline-5 { text-decoration: dashed underline; } + +.xterm-overline { + text-decoration: overline; +} + +.xterm-overline.xterm-underline-1 { text-decoration: overline underline; } +.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } +.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } +.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } +.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } + +.xterm-strikethrough { + text-decoration: line-through; +} + +.xterm-screen .xterm-decoration-container .xterm-decoration { + z-index: 6; + position: absolute; +} + +.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer { + z-index: 7; +} + +.xterm-decoration-overview-ruler { + z-index: 8; + position: absolute; + top: 0; + right: 0; + pointer-events: none; +} + +.xterm-decoration-top { + z-index: 2; + position: relative; +} + + + +/* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */ + +/* xterm.js customization: Override xterm's cursor style */ +.xterm .xterm-scrollable-element > .scrollbar { + cursor: default; +} + +/* Arrows */ +.xterm .xterm-scrollable-element > .scrollbar > .scra { + cursor: pointer; + font-size: 11px !important; +} + +.xterm .xterm-scrollable-element > .visible { + opacity: 1; + + /* Background rule added for IE9 - to allow clicks on dom node */ + background:rgba(0,0,0,0); + + transition: opacity 100ms linear; + /* In front of peek view */ + z-index: 11; +} +.xterm .xterm-scrollable-element > .invisible { + opacity: 0; + pointer-events: none; +} +.xterm .xterm-scrollable-element > .invisible.fade { + transition: opacity 800ms linear; +} + +/* Scrollable Content Inset Shadow */ +.xterm .xterm-scrollable-element > .shadow { + position: absolute; + display: none; +} +.xterm .xterm-scrollable-element > .shadow.top { + display: block; + top: 0; + left: 3px; + height: 3px; + width: 100%; + box-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset; +} +.xterm .xterm-scrollable-element > .shadow.left { + display: block; + top: 3px; + left: 0; + height: 100%; + width: 3px; + box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; +} +.xterm .xterm-scrollable-element > .shadow.top-left-corner { + display: block; + top: 0; + left: 0; + height: 3px; + width: 3px; +} +.xterm .xterm-scrollable-element > .shadow.top.left { + box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; +} diff --git a/Glint/Resources/WebRemote/xterm.mjs b/Glint/Resources/WebRemote/xterm.mjs new file mode 100644 index 0000000..d540256 --- /dev/null +++ b/Glint/Resources/WebRemote/xterm.mjs @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved. + * @license MIT + * + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * @license MIT + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var zs=Object.defineProperty;var Rl=Object.getOwnPropertyDescriptor;var Ll=(s,t)=>{for(var e in t)zs(s,e,{get:t[e],enumerable:!0})};var M=(s,t,e,i)=>{for(var r=i>1?void 0:i?Rl(t,e):t,n=s.length-1,o;n>=0;n--)(o=s[n])&&(r=(i?o(t,e,r):o(r))||r);return i&&r&&zs(t,e,r),r},S=(s,t)=>(e,i)=>t(e,i,s);var Gs="Terminal input",mi={get:()=>Gs,set:s=>Gs=s},$s="Too much output to announce, navigate to rows manually to read",_i={get:()=>$s,set:s=>$s=s};function Al(s){return s.replace(/\r?\n/g,"\r")}function kl(s,t){return t?"\x1B[200~"+s+"\x1B[201~":s}function Vs(s,t){s.clipboardData&&s.clipboardData.setData("text/plain",t.selectionText),s.preventDefault()}function qs(s,t,e,i){if(s.stopPropagation(),s.clipboardData){let r=s.clipboardData.getData("text/plain");Cn(r,t,e,i)}}function Cn(s,t,e,i){s=Al(s),s=kl(s,e.decPrivateModes.bracketedPasteMode&&i.rawOptions.ignoreBracketedPasteMode!==!0),e.triggerDataEvent(s,!0),t.value=""}function Mn(s,t,e){let i=e.getBoundingClientRect(),r=s.clientX-i.left-10,n=s.clientY-i.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 Pn(s,t,e,i,r){Mn(s,t,e),r&&i.rightClickSelect(s),t.value=i.selectionText,t.select()}function Ce(s){return s>65535?(s-=65536,String.fromCharCode((s>>10)+55296)+String.fromCharCode(s%1024+56320)):String.fromCharCode(s)}function It(s,t=0,e=s.length){let i="";for(let r=t;r65535?(n-=65536,i+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):i+=String.fromCharCode(n)}return i}var er=class{constructor(){this._interim=0}clear(){this._interim=0}decode(t,e){let i=t.length;if(!i)return 0;let r=0,n=0;if(this._interim){let o=t.charCodeAt(n++);56320<=o&&o<=57343?e[r++]=(this._interim-55296)*1024+o-56320+65536:(e[r++]=this._interim,e[r++]=o),this._interim=0}for(let o=n;o=i)return this._interim=l,r;let a=t.charCodeAt(o);56320<=a&&a<=57343?e[r++]=(l-55296)*1024+a-56320+65536:(e[r++]=l,e[r++]=a);continue}l!==65279&&(e[r++]=l)}return r}},tr=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(t,e){let i=t.length;if(!i)return 0;let r=0,n,o,l,a,u=0,h=0;if(this.interim[0]){let _=!1,p=this.interim[0];p&=(p&224)===192?31:(p&240)===224?15:7;let m=0,f;for(;(f=this.interim[++m]&63)&&m<4;)p<<=6,p|=f;let A=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,R=A-m;for(;h=i)return 0;if(f=t[h++],(f&192)!==128){h--,_=!0;break}else this.interim[m++]=f,p<<=6,p|=f&63}_||(A===2?p<128?h--:e[r++]=p:A===3?p<2048||p>=55296&&p<=57343||p===65279||(e[r++]=p):p<65536||p>1114111||(e[r++]=p)),this.interim.fill(0)}let c=i-4,d=h;for(;d=i)return this.interim[0]=n,r;if(o=t[d++],(o&192)!==128){d--;continue}if(u=(n&31)<<6|o&63,u<128){d--;continue}e[r++]=u}else if((n&240)===224){if(d>=i)return this.interim[0]=n,r;if(o=t[d++],(o&192)!==128){d--;continue}if(d>=i)return this.interim[0]=n,this.interim[1]=o,r;if(l=t[d++],(l&192)!==128){d--;continue}if(u=(n&15)<<12|(o&63)<<6|l&63,u<2048||u>=55296&&u<=57343||u===65279)continue;e[r++]=u}else if((n&248)===240){if(d>=i)return this.interim[0]=n,r;if(o=t[d++],(o&192)!==128){d--;continue}if(d>=i)return this.interim[0]=n,this.interim[1]=o,r;if(l=t[d++],(l&192)!==128){d--;continue}if(d>=i)return this.interim[0]=n,this.interim[1]=o,this.interim[2]=l,r;if(a=t[d++],(a&192)!==128){d--;continue}if(u=(n&7)<<18|(o&63)<<12|(l&63)<<6|a&63,u<65536||u>1114111)continue;e[r++]=u}}return r}};var ir="";var we=" ";var De=class s{constructor(){this.fg=0;this.bg=0;this.extended=new rt}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 s;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}},rt=class s{constructor(t=0,e=0){this._ext=0;this._urlId=0;this._ext=t,this._urlId=e}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 s(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}};var q=class s extends De{constructor(){super(...arguments);this.content=0;this.fg=0;this.bg=0;this.extended=new rt;this.combinedData=""}static fromCharData(e){let i=new s;return i.setFromCharData(e),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?Ce(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(e){this.fg=e[0],this.bg=0;let i=!1;if(e[1].length>2)i=!0;else if(e[1].length===2){let r=e[1].charCodeAt(0);if(55296<=r&&r<=56319){let n=e[1].charCodeAt(1);56320<=n&&n<=57343?this.content=(r-55296)*1024+n-56320+65536|e[2]<<22:i=!0}else i=!0}else this.content=e[1].charCodeAt(0)|e[2]<<22;i&&(this.combinedData=e[1],this.content=2097152|e[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}};var js="di$target",Hn="di$dependencies",Fn=new Map;function Xs(s){return s[Hn]||[]}function ie(s){if(Fn.has(s))return Fn.get(s);let t=function(e,i,r){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");Pl(t,e,r)};return t._id=s,Fn.set(s,t),t}function Pl(s,t,e){t[js]===t?t[Hn].push({id:s,index:e}):(t[Hn]=[{id:s,index:e}],t[js]=t)}var F=ie("BufferService"),rr=ie("CoreMouseService"),ge=ie("CoreService"),Zs=ie("CharsetService"),xt=ie("InstantiationService");var nr=ie("LogService"),H=ie("OptionsService"),sr=ie("OscLinkService"),Js=ie("UnicodeService"),Be=ie("DecorationService");var wt=class{constructor(t,e,i){this._bufferService=t;this._optionsService=e;this._oscLinkService=i}provideLinks(t,e){let i=this._bufferService.buffer.lines.get(t-1);if(!i){e(void 0);return}let r=[],n=this._optionsService.rawOptions.linkHandler,o=new q,l=i.getTrimmedLength(),a=-1,u=-1,h=!1;for(let c=0;cn?n.activate(m,f,_):Ol(m,f),hover:(m,f)=>n?.hover?.(m,f,_),leave:(m,f)=>n?.leave?.(m,f,_)})}h=!1,o.hasExtendedAttrs()&&o.extended.urlId?(u=c,a=o.extended.urlId):(u=-1,a=-1)}}e(r)}};wt=M([S(0,F),S(1,H),S(2,sr)],wt);function Ol(s,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 nt=ie("CharSizeService"),ae=ie("CoreBrowserService"),Dt=ie("MouseService"),ce=ie("RenderService"),Qs=ie("SelectionService"),or=ie("CharacterJoinerService"),Re=ie("ThemeService"),lr=ie("LinkProviderService");var Wn=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(t){setTimeout(()=>{throw t.stack?ar.isErrorNoTelemetry(t)?new ar(t.message+` + +`+t.stack):new Error(t.message+` + +`+t.stack):t},0)}}addListener(t){return this.listeners.push(t),()=>{this._removeListener(t)}}emit(t){this.listeners.forEach(e=>{e(t)})}_removeListener(t){this.listeners.splice(this.listeners.indexOf(t),1)}setUnexpectedErrorHandler(t){this.unexpectedErrorHandler=t}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(t){this.unexpectedErrorHandler(t),this.emit(t)}onUnexpectedExternalError(t){this.unexpectedErrorHandler(t)}},Bl=new Wn;function Lt(s){Nl(s)||Bl.onUnexpectedError(s)}var Un="Canceled";function Nl(s){return s instanceof bi?!0:s instanceof Error&&s.name===Un&&s.message===Un}var bi=class extends Error{constructor(){super(Un),this.name=this.message}};function eo(s){return s?new Error(`Illegal argument: ${s}`):new Error("Illegal argument")}var ar=class s extends Error{constructor(t){super(t),this.name="CodeExpectedError"}static fromError(t){if(t instanceof s)return t;let e=new s;return e.message=t.message,e.stack=t.stack,e}static isErrorNoTelemetry(t){return t.name==="CodeExpectedError"}},Rt=class s extends Error{constructor(t){super(t||"An unexpected bug occurred."),Object.setPrototypeOf(this,s.prototype)}};function Fl(s,t,e=0,i=s.length){let r=e,n=i;for(;r{function s(a){return a<0}l.isLessThan=s;function t(a){return a<=0}l.isLessThanOrEqual=t;function e(a){return a>0}l.isGreaterThan=e;function i(a){return a===0}l.isNeitherLessOrGreaterThan=i,l.greaterThan=1,l.lessThan=-1,l.neitherLessOrGreaterThan=0})(ro||={});function no(s,t){return(e,i)=>t(s(e),s(i))}var so=(s,t)=>s-t;var At=class At{constructor(t){this.iterate=t}forEach(t){this.iterate(e=>(t(e),!0))}toArray(){let t=[];return this.iterate(e=>(t.push(e),!0)),t}filter(t){return new At(e=>this.iterate(i=>t(i)?e(i):!0))}map(t){return new At(e=>this.iterate(i=>e(t(i))))}some(t){let e=!1;return this.iterate(i=>(e=t(i),!e)),e}findFirst(t){let e;return this.iterate(i=>t(i)?(e=i,!1):!0),e}findLast(t){let e;return this.iterate(i=>(t(i)&&(e=i),!0)),e}findLastMaxBy(t){let e,i=!0;return this.iterate(r=>((i||ro.isGreaterThan(t(r,e)))&&(i=!1,e=r),!0)),e}};At.empty=new At(t=>{});var io=At;function co(s,t){let e=Object.create(null);for(let i of s){let r=t(i),n=e[r];n||(n=e[r]=[]),n.push(i)}return e}var lo,ao,oo=class{constructor(t,e){this.toKey=e;this._map=new Map;this[lo]="SetWithKey";for(let i of t)this.add(i)}get size(){return this._map.size}add(t){let e=this.toKey(t);return this._map.set(e,t),this}delete(t){return this._map.delete(this.toKey(t))}has(t){return this._map.has(this.toKey(t))}*entries(){for(let t of this._map.values())yield[t,t]}keys(){return this.values()}*values(){for(let t of this._map.values())yield t}clear(){this._map.clear()}forEach(t,e){this._map.forEach(i=>t.call(e,i,i,this))}[(ao=Symbol.iterator,lo=Symbol.toStringTag,ao)](){return this.values()}};var ur=class{constructor(){this.map=new Map}add(t,e){let i=this.map.get(t);i||(i=new Set,this.map.set(t,i)),i.add(e)}delete(t,e){let i=this.map.get(t);i&&(i.delete(e),i.size===0&&this.map.delete(t))}forEach(t,e){let i=this.map.get(t);i&&i.forEach(e)}get(t){let e=this.map.get(t);return e||new Set}};function Kn(s,t){let e=this,i=!1,r;return function(){if(i)return r;if(i=!0,t)try{r=s.apply(e,arguments)}finally{t()}else r=s.apply(e,arguments);return r}}var zn;(O=>{function s(I){return I&&typeof I=="object"&&typeof I[Symbol.iterator]=="function"}O.is=s;let t=Object.freeze([]);function e(){return t}O.empty=e;function*i(I){yield I}O.single=i;function r(I){return s(I)?I:i(I)}O.wrap=r;function n(I){return I||t}O.from=n;function*o(I){for(let k=I.length-1;k>=0;k--)yield I[k]}O.reverse=o;function l(I){return!I||I[Symbol.iterator]().next().done===!0}O.isEmpty=l;function a(I){return I[Symbol.iterator]().next().value}O.first=a;function u(I,k){let P=0;for(let oe of I)if(k(oe,P++))return!0;return!1}O.some=u;function h(I,k){for(let P of I)if(k(P))return P}O.find=h;function*c(I,k){for(let P of I)k(P)&&(yield P)}O.filter=c;function*d(I,k){let P=0;for(let oe of I)yield k(oe,P++)}O.map=d;function*_(I,k){let P=0;for(let oe of I)yield*k(oe,P++)}O.flatMap=_;function*p(...I){for(let k of I)yield*k}O.concat=p;function m(I,k,P){let oe=P;for(let Me of I)oe=k(oe,Me);return oe}O.reduce=m;function*f(I,k,P=I.length){for(k<0&&(k+=I.length),P<0?P+=I.length:P>I.length&&(P=I.length);ki.source!==null&&!this.getRootParent(i,t).isSingleton).flatMap(([i])=>i)}computeLeakingDisposables(t=10,e){let i;if(e)i=e;else{let a=new Map,u=[...this.livingDisposables.values()].filter(c=>c.source!==null&&!this.getRootParent(c,a).isSingleton);if(u.length===0)return;let h=new Set(u.map(c=>c.value));if(i=u.filter(c=>!(c.parent&&h.has(c.parent))),i.length===0)throw new Error("There are cyclic diposable chains!")}if(!i)return;function r(a){function u(c,d){for(;c.length>0&&d.some(_=>typeof _=="string"?_===c[0]:c[0].match(_));)c.shift()}let h=a.source.split(` +`).map(c=>c.trim().replace("at ","")).filter(c=>c!=="");return u(h,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),h.reverse()}let n=new ur;for(let a of i){let u=r(a);for(let h=0;h<=u.length;h++)n.add(u.slice(0,h).join(` +`),a)}i.sort(no(a=>a.idx,so));let o="",l=0;for(let a of i.slice(0,t)){l++;let u=r(a),h=[];for(let c=0;cr(f)[c]),f=>f);delete m[u[c]];for(let[f,A]of Object.entries(m))h.unshift(` - stacktraces of ${A.length} other leaks continue with ${f}`);h.unshift(d)}o+=` + + +==================== Leaking disposable ${l}/${i.length}: ${a.value.constructor.name} ==================== +${h.join(` +`)} +============================================================ + +`}return i.length>t&&(o+=` + + +... and ${i.length-t} more leaking disposables + +`),{leaks:i,details:o}}};hr.idx=0;var uo=hr;function Ul(s){dt=s}if(Wl){let s="__is_disposable_tracked__";Ul(new class{trackDisposable(t){let e=new Error("Potentially leaked disposable").stack;setTimeout(()=>{t[s]||console.log(e)},3e3)}setParent(t,e){if(t&&t!==D.None)try{t[s]=!0}catch{}}markAsDisposed(t){if(t&&t!==D.None)try{t[s]=!0}catch{}}markAsSingleton(t){}})}function fr(s){return dt?.trackDisposable(s),s}function pr(s){dt?.markAsDisposed(s)}function vi(s,t){dt?.setParent(s,t)}function Kl(s,t){if(dt)for(let e of s)dt.setParent(e,t)}function Gn(s){return dt?.markAsSingleton(s),s}function Ne(s){if(zn.is(s)){let t=[];for(let e of s)if(e)try{e.dispose()}catch(i){t.push(i)}if(t.length===1)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(s)?[]:s}else if(s)return s.dispose(),s}function ho(...s){let t=C(()=>Ne(s));return Kl(s,t),t}function C(s){let t=fr({dispose:Kn(()=>{pr(t),s()})});return t}var dr=class dr{constructor(){this._toDispose=new Set;this._isDisposed=!1;fr(this)}dispose(){this._isDisposed||(pr(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{Ne(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 vi(t,this),this._isDisposed?dr.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),vi(t,null))}};dr.DISABLE_DISPOSED_WARNING=!1;var Ee=dr,D=class{constructor(){this._store=new Ee;fr(this),vi(this._store,this)}dispose(){pr(this),this._store.dispose()}_register(t){if(t===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(t)}};D.None=Object.freeze({dispose(){}});var ye=class{constructor(){this._isDisposed=!1;fr(this)}get value(){return this._isDisposed?void 0:this._value}set value(t){this._isDisposed||t===this._value||(this._value?.dispose(),t&&vi(t,this),this._value=t)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,pr(this),this._value?.dispose(),this._value=void 0}clearAndLeak(){let t=this._value;return this._value=void 0,t&&vi(t,null),t}};var fe=typeof window=="object"?window:globalThis;var kt=class kt{constructor(t){this.element=t,this.next=kt.Undefined,this.prev=kt.Undefined}};kt.Undefined=new kt(void 0);var G=kt,Ct=class{constructor(){this._first=G.Undefined;this._last=G.Undefined;this._size=0}get size(){return this._size}isEmpty(){return this._first===G.Undefined}clear(){let t=this._first;for(;t!==G.Undefined;){let e=t.next;t.prev=G.Undefined,t.next=G.Undefined,t=e}this._first=G.Undefined,this._last=G.Undefined,this._size=0}unshift(t){return this._insert(t,!1)}push(t){return this._insert(t,!0)}_insert(t,e){let i=new G(t);if(this._first===G.Undefined)this._first=i,this._last=i;else if(e){let n=this._last;this._last=i,i.prev=n,n.next=i}else{let n=this._first;this._first=i,i.next=n,n.prev=i}this._size+=1;let r=!1;return()=>{r||(r=!0,this._remove(i))}}shift(){if(this._first!==G.Undefined){let t=this._first.element;return this._remove(this._first),t}}pop(){if(this._last!==G.Undefined){let t=this._last.element;return this._remove(this._last),t}}_remove(t){if(t.prev!==G.Undefined&&t.next!==G.Undefined){let e=t.prev;e.next=t.next,t.next.prev=e}else t.prev===G.Undefined&&t.next===G.Undefined?(this._first=G.Undefined,this._last=G.Undefined):t.next===G.Undefined?(this._last=this._last.prev,this._last.next=G.Undefined):t.prev===G.Undefined&&(this._first=this._first.next,this._first.prev=G.Undefined);this._size-=1}*[Symbol.iterator](){let t=this._first;for(;t!==G.Undefined;)yield t.element,t=t.next}};var zl=globalThis.performance&&typeof globalThis.performance.now=="function",mr=class s{static create(t){return new s(t)}constructor(t){this._now=zl&&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}};var Gl=!1,fo=!1,$l=!1,$;(Qe=>{Qe.None=()=>D.None;function t(y){if($l){let{onDidAddListener:T}=y,g=gi.create(),w=0;y.onDidAddListener=()=>{++w===2&&(console.warn("snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here"),g.print()),T?.()}}}function e(y,T){return d(y,()=>{},0,void 0,!0,void 0,T)}Qe.defer=e;function i(y){return(T,g=null,w)=>{let E=!1,x;return x=y(N=>{if(!E)return x?x.dispose():E=!0,T.call(g,N)},null,w),E&&x.dispose(),x}}Qe.once=i;function r(y,T,g){return h((w,E=null,x)=>y(N=>w.call(E,T(N)),null,x),g)}Qe.map=r;function n(y,T,g){return h((w,E=null,x)=>y(N=>{T(N),w.call(E,N)},null,x),g)}Qe.forEach=n;function o(y,T,g){return h((w,E=null,x)=>y(N=>T(N)&&w.call(E,N),null,x),g)}Qe.filter=o;function l(y){return y}Qe.signal=l;function a(...y){return(T,g=null,w)=>{let E=ho(...y.map(x=>x(N=>T.call(g,N))));return c(E,w)}}Qe.any=a;function u(y,T,g,w){let E=g;return r(y,x=>(E=T(E,x),E),w)}Qe.reduce=u;function h(y,T){let g,w={onWillAddFirstListener(){g=y(E.fire,E)},onDidRemoveLastListener(){g?.dispose()}};T||t(w);let E=new v(w);return T?.add(E),E.event}function c(y,T){return T instanceof Array?T.push(y):T&&T.add(y),y}function d(y,T,g=100,w=!1,E=!1,x,N){let Z,te,Oe,ze=0,le,et={leakWarningThreshold:x,onWillAddFirstListener(){Z=y(ht=>{ze++,te=T(te,ht),w&&!Oe&&(me.fire(te),te=void 0),le=()=>{let fi=te;te=void 0,Oe=void 0,(!w||ze>1)&&me.fire(fi),ze=0},typeof g=="number"?(clearTimeout(Oe),Oe=setTimeout(le,g)):Oe===void 0&&(Oe=0,queueMicrotask(le))})},onWillRemoveListener(){E&&ze>0&&le?.()},onDidRemoveLastListener(){le=void 0,Z.dispose()}};N||t(et);let me=new v(et);return N?.add(me),me.event}Qe.debounce=d;function _(y,T=0,g){return Qe.debounce(y,(w,E)=>w?(w.push(E),w):[E],T,void 0,!0,void 0,g)}Qe.accumulate=_;function p(y,T=(w,E)=>w===E,g){let w=!0,E;return o(y,x=>{let N=w||!T(x,E);return w=!1,E=x,N},g)}Qe.latch=p;function m(y,T,g){return[Qe.filter(y,T,g),Qe.filter(y,w=>!T(w),g)]}Qe.split=m;function f(y,T=!1,g=[],w){let E=g.slice(),x=y(te=>{E?E.push(te):Z.fire(te)});w&&w.add(x);let N=()=>{E?.forEach(te=>Z.fire(te)),E=null},Z=new v({onWillAddFirstListener(){x||(x=y(te=>Z.fire(te)),w&&w.add(x))},onDidAddFirstListener(){E&&(T?setTimeout(N):N())},onDidRemoveLastListener(){x&&x.dispose(),x=null}});return w&&w.add(Z),Z.event}Qe.buffer=f;function A(y,T){return(w,E,x)=>{let N=T(new O);return y(function(Z){let te=N.evaluate(Z);te!==R&&w.call(E,te)},void 0,x)}}Qe.chain=A;let R=Symbol("HaltChainable");class O{constructor(){this.steps=[]}map(T){return this.steps.push(T),this}forEach(T){return this.steps.push(g=>(T(g),g)),this}filter(T){return this.steps.push(g=>T(g)?g:R),this}reduce(T,g){let w=g;return this.steps.push(E=>(w=T(w,E),w)),this}latch(T=(g,w)=>g===w){let g=!0,w;return this.steps.push(E=>{let x=g||!T(E,w);return g=!1,w=E,x?E:R}),this}evaluate(T){for(let g of this.steps)if(T=g(T),T===R)break;return T}}function I(y,T,g=w=>w){let w=(...Z)=>N.fire(g(...Z)),E=()=>y.on(T,w),x=()=>y.removeListener(T,w),N=new v({onWillAddFirstListener:E,onDidRemoveLastListener:x});return N.event}Qe.fromNodeEventEmitter=I;function k(y,T,g=w=>w){let w=(...Z)=>N.fire(g(...Z)),E=()=>y.addEventListener(T,w),x=()=>y.removeEventListener(T,w),N=new v({onWillAddFirstListener:E,onDidRemoveLastListener:x});return N.event}Qe.fromDOMEventEmitter=k;function P(y){return new Promise(T=>i(y)(T))}Qe.toPromise=P;function oe(y){let T=new v;return y.then(g=>{T.fire(g)},()=>{T.fire(void 0)}).finally(()=>{T.dispose()}),T.event}Qe.fromPromise=oe;function Me(y,T){return y(g=>T.fire(g))}Qe.forward=Me;function Pe(y,T,g){return T(g),y(w=>T(w))}Qe.runAndSubscribe=Pe;class Ke{constructor(T,g){this._observable=T;this._counter=0;this._hasChanged=!1;let w={onWillAddFirstListener:()=>{T.addObserver(this)},onDidRemoveLastListener:()=>{T.removeObserver(this)}};g||t(w),this.emitter=new v(w),g&&g.add(this.emitter)}beginUpdate(T){this._counter++}handlePossibleChange(T){}handleChange(T,g){this._hasChanged=!0}endUpdate(T){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function di(y,T){return new Ke(y,T).emitter.event}Qe.fromObservable=di;function V(y){return(T,g,w)=>{let E=0,x=!1,N={beginUpdate(){E++},endUpdate(){E--,E===0&&(y.reportChanges(),x&&(x=!1,T.call(g)))},handlePossibleChange(){},handleChange(){x=!0}};y.addObserver(N),y.reportChanges();let Z={dispose(){y.removeObserver(N)}};return w instanceof Ee?w.add(Z):Array.isArray(w)&&w.push(Z),Z}}Qe.fromObservableLight=V})($||={});var Mt=class Mt{constructor(t){this.listenerCount=0;this.invocationCount=0;this.elapsedOverall=0;this.durations=[];this.name=`${t}_${Mt._idPool++}`,Mt.all.add(this)}start(t){this._stopWatch=new mr,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}}};Mt.all=new Set,Mt._idPool=0;var $n=Mt,po=-1;var br=class br{constructor(t,e,i=(br._idPool++).toString(16).padStart(3,"0")){this._errorHandler=t;this.threshold=e;this.name=i;this._warnCountdown=0}dispose(){this._stacks?.clear()}check(t,e){let i=this.threshold;if(i<=0||e{let n=this._stacks.get(t.value)||0;this._stacks.set(t.value,n-1)}}getMostFrequentStack(){if(!this._stacks)return;let t,e=0;for(let[i,r]of this._stacks)(!t||e{if(s instanceof Pt)t(s);else for(let e=0;e{s.length!==0&&(console.warn("[LEAKING LISTENERS] GC'ed these listeners that were NOT yet disposed:"),console.warn(s.join(` +`)),s.length=0)},3e3),_r=new FinalizationRegistry(t=>{typeof t=="string"&&s.push(t)})}var v=class{constructor(t){this._size=0;this._options=t,this._leakageMon=po>0||this._options?.leakWarningThreshold?new Vn(t?.onListenerError??Lt,this._options?.leakWarningThreshold??po):void 0,this._perfMon=this._options?._profName?new $n(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(fo){let t=this._listeners;queueMicrotask(()=>{Yl(t,e=>e.stack?.print())})}this._listeners=void 0,this._size=0}this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose()}}get event(){return this._event??=(t,e,i)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let a=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(a);let u=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],h=new Yn(`${a}. HINT: Stack shows most frequent listener (${u[1]}-times)`,u[0]);return(this._options?.onListenerError||Lt)(h),D.None}if(this._disposed)return D.None;e&&(t=t.bind(e));let r=new Pt(t),n,o;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(r.stack=gi.create(),n=this._leakageMon.check(r.stack,this._size+1)),fo&&(r.stack=o??gi.create()),this._listeners?this._listeners instanceof Pt?(this._deliveryQueue??=new jn,this._listeners=[this._listeners,r]):this._listeners.push(r):(this._options?.onWillAddFirstListener?.(this),this._listeners=r,this._options?.onDidAddFirstListener?.(this)),this._size++;let l=C(()=>{_r?.unregister(l),n?.(),this._removeListener(r)});if(i instanceof Ee?i.add(l):Array.isArray(i)&&i.push(l),_r){let a=new Error().stack.split(` +`).slice(2,3).join(` +`).trim(),u=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(a);_r.register(l,u?.[2]??a,l)}return l},this._event}_removeListener(t){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 e=this._listeners,i=e.indexOf(t);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--,e[i]=void 0;let r=this._deliveryQueue.current===this;if(this._size*ql<=e.length){let n=0;for(let o=0;o0}};var jn=class{constructor(){this.i=-1;this.end=0}enqueue(t,e,i){this.i=0,this.end=i,this.current=t,this.value=e}reset(){this.i=this.end,this.current=void 0,this.value=void 0}};var gr=class gr{constructor(){this.mapWindowIdToZoomLevel=new Map;this._onDidChangeZoomLevel=new v;this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event;this.mapWindowIdToZoomFactor=new Map;this._onDidChangeFullscreen=new v;this.onDidChangeFullscreen=this._onDidChangeFullscreen.event;this.mapWindowIdToFullScreen=new Map}getZoomLevel(t){return this.mapWindowIdToZoomLevel.get(this.getWindowId(t))??0}setZoomLevel(t,e){if(this.getZoomLevel(e)===t)return;let i=this.getWindowId(e);this.mapWindowIdToZoomLevel.set(i,t),this._onDidChangeZoomLevel.fire(i)}getZoomFactor(t){return this.mapWindowIdToZoomFactor.get(this.getWindowId(t))??1}setZoomFactor(t,e){this.mapWindowIdToZoomFactor.set(this.getWindowId(e),t)}setFullscreen(t,e){if(this.isFullscreen(e)===t)return;let i=this.getWindowId(e);this.mapWindowIdToFullScreen.set(i,t),this._onDidChangeFullscreen.fire(i)}isFullscreen(t){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(t))}getWindowId(t){return t.vscodeWindowId}};gr.INSTANCE=new gr;var Si=gr;function Xl(s,t,e){typeof t=="string"&&(t=s.matchMedia(t)),t.addEventListener("change",e)}var Eu=Si.INSTANCE.onDidChangeZoomLevel;function mo(s){return Si.INSTANCE.getZoomFactor(s)}var Tu=Si.INSTANCE.onDidChangeFullscreen,Ot=typeof navigator=="object"?navigator.userAgent:"",Ei=Ot.indexOf("Firefox")>=0,Bt=Ot.indexOf("AppleWebKit")>=0,Ti=Ot.indexOf("Chrome")>=0,Sr=!Ti&&Ot.indexOf("Safari")>=0;var Iu=Ot.indexOf("Electron/")>=0,yu=Ot.indexOf("Android")>=0,vr=!1;if(typeof fe.matchMedia=="function"){let s=fe.matchMedia("(display-mode: standalone) or (display-mode: window-controls-overlay)"),t=fe.matchMedia("(display-mode: fullscreen)");vr=s.matches,Xl(fe,s,({matches:e})=>{vr&&t.matches||(vr=e)})}function _o(){return vr}var Nt="en",yr=!1,xr=!1,Ii=!1,Zl=!1,vo=!1,go=!1,Jl=!1,Ql=!1,ea=!1,ta=!1,Tr,Ir=Nt,bo=Nt,ia,$e,Ve=globalThis,xe;typeof Ve.vscode<"u"&&typeof Ve.vscode.process<"u"?xe=Ve.vscode.process:typeof process<"u"&&typeof process?.versions?.node=="string"&&(xe=process);var So=typeof xe?.versions?.electron=="string",ra=So&&xe?.type==="renderer";if(typeof xe=="object"){yr=xe.platform==="win32",xr=xe.platform==="darwin",Ii=xe.platform==="linux",Zl=Ii&&!!xe.env.SNAP&&!!xe.env.SNAP_REVISION,Jl=So,ea=!!xe.env.CI||!!xe.env.BUILD_ARTIFACTSTAGINGDIRECTORY,Tr=Nt,Ir=Nt;let s=xe.env.VSCODE_NLS_CONFIG;if(s)try{let t=JSON.parse(s);Tr=t.userLocale,bo=t.osLocale,Ir=t.resolvedLanguage||Nt,ia=t.languagePack?.translationsConfigFile}catch{}vo=!0}else typeof navigator=="object"&&!ra?($e=navigator.userAgent,yr=$e.indexOf("Windows")>=0,xr=$e.indexOf("Macintosh")>=0,Ql=($e.indexOf("Macintosh")>=0||$e.indexOf("iPad")>=0||$e.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,Ii=$e.indexOf("Linux")>=0,ta=$e?.indexOf("Mobi")>=0,go=!0,Ir=globalThis._VSCODE_NLS_LANGUAGE||Nt,Tr=navigator.language.toLowerCase(),bo=Tr):console.error("Unable to resolve platform.");var Xn=0;xr?Xn=1:yr?Xn=3:Ii&&(Xn=2);var wr=yr,Te=xr,Zn=Ii;var Dr=vo;var na=go&&typeof Ve.importScripts=="function",xu=na?Ve.origin:void 0;var Fe=$e,st=Ir,sa;(i=>{function s(){return st}i.value=s;function t(){return st.length===2?st==="en":st.length>=3?st[0]==="e"&&st[1]==="n"&&st[2]==="-":!1}i.isDefaultVariant=t;function e(){return st==="en"}i.isDefault=e})(sa||={});var oa=typeof Ve.postMessage=="function"&&!Ve.importScripts,Eo=(()=>{if(oa){let s=[];Ve.addEventListener("message",e=>{if(e.data&&e.data.vscodeScheduleAsyncWork)for(let i=0,r=s.length;i{let i=++t;s.push({id:i,callback:e}),Ve.postMessage({vscodeScheduleAsyncWork:i},"*")}}return s=>setTimeout(s)})();var la=!!(Fe&&Fe.indexOf("Chrome")>=0),wu=!!(Fe&&Fe.indexOf("Firefox")>=0),Du=!!(!la&&Fe&&Fe.indexOf("Safari")>=0),Ru=!!(Fe&&Fe.indexOf("Edg/")>=0),Lu=!!(Fe&&Fe.indexOf("Android")>=0);var ot=typeof navigator=="object"?navigator:{},aa={clipboard:{writeText:Dr||document.queryCommandSupported&&document.queryCommandSupported("copy")||!!(ot&&ot.clipboard&&ot.clipboard.writeText),readText:Dr||!!(ot&&ot.clipboard&&ot.clipboard.readText)},keyboard:Dr||_o()?0:ot.keyboard||Sr?1:2,touch:"ontouchstart"in fe||ot.maxTouchPoints>0,pointerEvents:fe.PointerEvent&&("ontouchstart"in fe||navigator.maxTouchPoints>0)};var yi=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(t,e){this._keyCodeToStr[t]=e,this._strToKeyCode[e.toLowerCase()]=t}keyCodeToStr(t){return this._keyCodeToStr[t]}strToKeyCode(t){return this._strToKeyCode[t.toLowerCase()]||0}},Jn=new yi,To=new yi,Io=new yi,yo=new Array(230);var Qn;(o=>{function s(l){return Jn.keyCodeToStr(l)}o.toString=s;function t(l){return Jn.strToKeyCode(l)}o.fromString=t;function e(l){return To.keyCodeToStr(l)}o.toUserSettingsUS=e;function i(l){return Io.keyCodeToStr(l)}o.toUserSettingsGeneral=i;function r(l){return To.strToKeyCode(l)||Io.strToKeyCode(l)}o.fromUserSettings=r;function n(l){if(l>=98&&l<=113)return null;switch(l){case 16:return"Up";case 18:return"Down";case 15:return"Left";case 17:return"Right"}return Jn.keyCodeToStr(l)}o.toElectronAccelerator=n})(Qn||={});var Rr=class s{constructor(t,e,i,r,n){this.ctrlKey=t;this.shiftKey=e;this.altKey=i;this.metaKey=r;this.keyCode=n}equals(t){return t instanceof s&&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",e=this.shiftKey?"1":"0",i=this.altKey?"1":"0",r=this.metaKey?"1":"0";return`K${t}${e}${i}${r}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new es([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}};var es=class{constructor(t){if(t.length===0)throw eo("chords");this.chords=t}getHashCode(){let t="";for(let e=0,i=this.chords.length;e{function s(r){return r===i.None||r===i.Cancelled||r instanceof ts?!0:!r||typeof r!="object"?!1:typeof r.isCancellationRequested=="boolean"&&typeof r.onCancellationRequested=="function"}i.isCancellationToken=s,i.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:$.None}),i.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:Do})})(ma||={});var ts=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?Do:(this._emitter||(this._emitter=new v),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}};var _a=Symbol("MicrotaskDelay");var Ye=class{constructor(t,e){this._isDisposed=!1;this._token=-1,typeof t=="function"&&typeof e=="number"&&this.setIfNotSet(t,e)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(t,e){if(this._isDisposed)throw new Rt("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,t()},e)}setIfNotSet(t,e){if(this._isDisposed)throw new Rt("Calling 'setIfNotSet' on a disposed TimeoutTimer");this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,t()},e))}},kr=class{constructor(){this.disposable=void 0;this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(t,e,i=globalThis){if(this.isDisposed)throw new Rt("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();let r=i.setInterval(()=>{t()},e);this.disposable=C(()=>{i.clearInterval(r),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}};var ba,Ar;(function(){typeof globalThis.requestIdleCallback!="function"||typeof globalThis.cancelIdleCallback!="function"?Ar=(s,t)=>{Eo(()=>{if(e)return;let i=Date.now()+15;t(Object.freeze({didTimeout:!0,timeRemaining(){return Math.max(0,i-Date.now())}}))});let e=!1;return{dispose(){e||(e=!0)}}}:Ar=(s,t,e)=>{let i=s.requestIdleCallback(t,typeof e=="number"?{timeout:e}:void 0),r=!1;return{dispose(){r||(r=!0,s.cancelIdleCallback(i))}}},ba=s=>Ar(globalThis,s)})();var va;(e=>{async function s(i){let r,n=await Promise.all(i.map(o=>o.then(l=>l,l=>{r||(r=l)})));if(typeof r<"u")throw r;return n}e.settled=s;function t(i){return new Promise(async(r,n)=>{try{await i(r,n)}catch(o){n(o)}})}e.withAsyncBody=t})(va||={});var _e=class _e{static fromArray(t){return new _e(e=>{e.emitMany(t)})}static fromPromise(t){return new _e(async e=>{e.emitMany(await t)})}static fromPromises(t){return new _e(async e=>{await Promise.all(t.map(async i=>e.emitOne(await i)))})}static merge(t){return new _e(async e=>{await Promise.all(t.map(async i=>{for await(let r of i)e.emitOne(r)}))})}constructor(t,e){this._state=0,this._results=[],this._error=null,this._onReturn=e,this._onStateChanged=new v,queueMicrotask(async()=>{let i={emitOne:r=>this.emitOne(r),emitMany:r=>this.emitMany(r),reject:r=>this.reject(r)};try{await Promise.resolve(t(i)),this.resolve()}catch(r){this.reject(r)}finally{i.emitOne=void 0,i.emitMany=void 0,i.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,e){return new _e(async i=>{for await(let r of t)i.emitOne(e(r))})}map(t){return _e.map(this,t)}static filter(t,e){return new _e(async i=>{for await(let r of t)e(r)&&i.emitOne(r)})}filter(t){return _e.filter(this,t)}static coalesce(t){return _e.filter(t,e=>!!e)}coalesce(){return _e.coalesce(this)}static async toPromise(t){let e=[];for await(let i of t)e.push(i);return e}toPromise(){return _e.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())}};_e.EMPTY=_e.fromArray([]);var Ro=_e;function Lo(s){return 55296<=s&&s<=56319}function is(s){return 56320<=s&&s<=57343}function Ao(s,t){return(s-55296<<10)+(t-56320)+65536}function Mo(s){return ns(s,0)}function ns(s,t){switch(typeof s){case"object":return s===null?je(349,t):Array.isArray(s)?Ea(s,t):Ta(s,t);case"string":return Po(s,t);case"boolean":return Sa(s,t);case"number":return je(s,t);case"undefined":return je(937,t);default:return je(617,t)}}function je(s,t){return(t<<5)-t+s|0}function Sa(s,t){return je(s?433:863,t)}function Po(s,t){t=je(149417,t);for(let e=0,i=s.length;ens(i,e),t)}function Ta(s,t){return t=je(181387,t),Object.keys(s).sort().reduce((e,i)=>(e=Po(i,e),ns(s[i],e)),t)}function rs(s,t,e=32){let i=e-t,r=~((1<>>i)>>>0}function ko(s,t=0,e=s.byteLength,i=0){for(let r=0;re.toString(16).padStart(2,"0")).join(""):Ia((s>>>0).toString(16),t/4)}var Cr=class Cr{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 e=t.length;if(e===0)return;let i=this._buff,r=this._buffLen,n=this._leftoverHighSurrogate,o,l;for(n!==0?(o=n,l=-1,n=0):(o=t.charCodeAt(0),l=0);;){let a=o;if(Lo(o))if(l+1>>6,t[e++]=128|(i&63)>>>0):i<65536?(t[e++]=224|(i&61440)>>>12,t[e++]=128|(i&4032)>>>6,t[e++]=128|(i&63)>>>0):(t[e++]=240|(i&1835008)>>>18,t[e++]=128|(i&258048)>>>12,t[e++]=128|(i&4032)>>>6,t[e++]=128|(i&63)>>>0),e>=64&&(this._step(),e-=64,this._totalLen+=64,t[0]=t[64],t[1]=t[65],t[2]=t[66]),e}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()),wi(this._h0)+wi(this._h1)+wi(this._h2)+wi(this._h3)+wi(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,ko(this._buff,this._buffLen),this._buffLen>56&&(this._step(),ko(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=Cr._bigBlock32,e=this._buffDV;for(let c=0;c<64;c+=4)t.setUint32(c,e.getUint32(c,!1),!1);for(let c=64;c<320;c+=4)t.setUint32(c,rs(t.getUint32(c-12,!1)^t.getUint32(c-32,!1)^t.getUint32(c-56,!1)^t.getUint32(c-64,!1),1),!1);let i=this._h0,r=this._h1,n=this._h2,o=this._h3,l=this._h4,a,u,h;for(let c=0;c<80;c++)c<20?(a=r&n|~r&o,u=1518500249):c<40?(a=r^n^o,u=1859775393):c<60?(a=r&n|r&o|n&o,u=2400959708):(a=r^n^o,u=3395469782),h=rs(i,5)+a+l+u+t.getUint32(c*4,!1)&4294967295,l=o,o=n,n=rs(r,30),r=i,i=h;this._h0=this._h0+i&4294967295,this._h1=this._h1+r&4294967295,this._h2=this._h2+n&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+l&4294967295}};Cr._bigBlock32=new DataView(new ArrayBuffer(320));var Co=Cr;var{registerWindow:Bh,getWindow:be,getDocument:Nh,getWindows:Fh,getWindowsCount:Hh,getWindowId:Oo,getWindowById:Wh,hasWindow:Uh,onDidRegisterWindow:No,onWillUnregisterWindow:Kh,onDidUnregisterWindow:zh}=function(){let s=new Map;fe;let t={window:fe,disposables:new Ee};s.set(fe.vscodeWindowId,t);let e=new v,i=new v,r=new v;function n(o,l){return(typeof o=="number"?s.get(o):void 0)??(l?t:void 0)}return{onDidRegisterWindow:e.event,onWillUnregisterWindow:r.event,onDidUnregisterWindow:i.event,registerWindow(o){if(s.has(o.vscodeWindowId))return D.None;let l=new Ee,a={window:o,disposables:l.add(new Ee)};return s.set(o.vscodeWindowId,a),l.add(C(()=>{s.delete(o.vscodeWindowId),i.fire(o)})),l.add(L(o,Y.BEFORE_UNLOAD,()=>{r.fire(o)})),e.fire(a),l},getWindows(){return s.values()},getWindowsCount(){return s.size},getWindowId(o){return o.vscodeWindowId},hasWindow(o){return s.has(o)},getWindowById:n,getWindow(o){let l=o;if(l?.ownerDocument?.defaultView)return l.ownerDocument.defaultView.window;let a=o;return a?.view?a.view.window:fe},getDocument(o){return be(o).document}}}();var ss=class{constructor(t,e,i,r){this._node=t,this._type=e,this._handler=i,this._options=r||!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 L(s,t,e,i){return new ss(s,t,e,i)}function ya(s,t){return function(e){return t(new qe(s,e))}}function xa(s){return function(t){return s(new ft(t))}}var os=function(t,e,i,r){let n=i;return e==="click"||e==="mousedown"||e==="contextmenu"?n=ya(be(t),i):(e==="keydown"||e==="keypress"||e==="keyup")&&(n=xa(i)),L(t,e,n,r)};var wa,mt;var Mr=class extends kr{constructor(t){super(),this.defaultTarget=t&&be(t)}cancelAndSet(t,e,i){return super.cancelAndSet(t,e,i??this.defaultTarget)}},Di=class{constructor(t,e=0){this._runner=t,this.priority=e,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(t){Lt(t)}}static sort(t,e){return e.priority-t.priority}};(function(){let s=new Map,t=new Map,e=new Map,i=new Map,r=n=>{e.set(n,!1);let o=s.get(n)??[];for(t.set(n,o),s.set(n,[]),i.set(n,!0);o.length>0;)o.sort(Di.sort),o.shift().execute();i.set(n,!1)};mt=(n,o,l=0)=>{let a=Oo(n),u=new Di(o,l),h=s.get(a);return h||(h=[],s.set(a,h)),h.push(u),e.get(a)||(e.set(a,!0),n.requestAnimationFrame(()=>r(a))),u},wa=(n,o,l)=>{let a=Oo(n);if(i.get(a)){let u=new Di(o,l),h=t.get(a);return h||(h=[],t.set(a,h)),h.push(u),u}else return mt(n,o,l)}})();var pt=class pt{constructor(t,e){this.width=t;this.height=e}with(t=this.width,e=this.height){return t!==this.width||e!==this.height?new pt(t,e):this}static is(t){return typeof t=="object"&&typeof t.height=="number"&&typeof t.width=="number"}static lift(t){return t instanceof pt?t:new pt(t.width,t.height)}static equals(t,e){return t===e?!0:!t||!e?!1:t.width===e.width&&t.height===e.height}};pt.None=new pt(0,0);var Bo=pt;function Fo(s){let t=s.getBoundingClientRect(),e=be(s);return{left:t.left+e.scrollX,top:t.top+e.scrollY,width:t.width,height:t.height}}var Gh=new class{constructor(){this.mutationObservers=new Map}observe(s,t,e){let i=this.mutationObservers.get(s);i||(i=new Map,this.mutationObservers.set(s,i));let r=Mo(e),n=i.get(r);if(n)n.users+=1;else{let o=new v,l=new MutationObserver(u=>o.fire(u));l.observe(s,e);let a=n={users:1,observer:l,onDidMutate:o.event};t.add(C(()=>{a.users-=1,a.users===0&&(o.dispose(),l.disconnect(),i?.delete(r),i?.size===0&&this.mutationObservers.delete(s))})),i.set(r,n)}return n.onDidMutate}};var Y={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:Bt?"webkitAnimationStart":"animationstart",ANIMATION_END:Bt?"webkitAnimationEnd":"animationend",ANIMATION_ITERATION:Bt?"webkitAnimationIteration":"animationiteration"};var Da=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function Ho(s,t,e,...i){let r=Da.exec(t);if(!r)throw new Error("Bad use of emmet");let n=r[1]||"div",o;return s!=="http://www.w3.org/1999/xhtml"?o=document.createElementNS(s,n):o=document.createElement(n),r[3]&&(o.id=r[3]),r[4]&&(o.className=r[4].replace(/\./g," ").trim()),e&&Object.entries(e).forEach(([l,a])=>{typeof a>"u"||(/^on\w+$/.test(l)?o[l]=a:l==="selected"?a&&o.setAttribute(l,"true"):o.setAttribute(l,a))}),o.append(...i),o}function Ra(s,t,...e){return Ho("http://www.w3.org/1999/xhtml",s,t,...e)}Ra.SVG=function(s,t,...e){return Ho("http://www.w3.org/2000/svg",s,t,...e)};var ls=class{constructor(t){this.domNode=t;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(t){let e=Ie(t);this._maxWidth!==e&&(this._maxWidth=e,this.domNode.style.maxWidth=this._maxWidth)}setWidth(t){let e=Ie(t);this._width!==e&&(this._width=e,this.domNode.style.width=this._width)}setHeight(t){let e=Ie(t);this._height!==e&&(this._height=e,this.domNode.style.height=this._height)}setTop(t){let e=Ie(t);this._top!==e&&(this._top=e,this.domNode.style.top=this._top)}setLeft(t){let e=Ie(t);this._left!==e&&(this._left=e,this.domNode.style.left=this._left)}setBottom(t){let e=Ie(t);this._bottom!==e&&(this._bottom=e,this.domNode.style.bottom=this._bottom)}setRight(t){let e=Ie(t);this._right!==e&&(this._right=e,this.domNode.style.right=this._right)}setPaddingTop(t){let e=Ie(t);this._paddingTop!==e&&(this._paddingTop=e,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(t){let e=Ie(t);this._paddingLeft!==e&&(this._paddingLeft=e,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(t){let e=Ie(t);this._paddingBottom!==e&&(this._paddingBottom=e,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(t){let e=Ie(t);this._paddingRight!==e&&(this._paddingRight=e,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(t){this._fontFamily!==t&&(this._fontFamily=t,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(t){this._fontWeight!==t&&(this._fontWeight=t,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(t){let e=Ie(t);this._fontSize!==e&&(this._fontSize=e,this.domNode.style.fontSize=this._fontSize)}setFontStyle(t){this._fontStyle!==t&&(this._fontStyle=t,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(t){this._fontFeatureSettings!==t&&(this._fontFeatureSettings=t,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(t){this._fontVariationSettings!==t&&(this._fontVariationSettings=t,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(t){this._textDecoration!==t&&(this._textDecoration=t,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(t){let e=Ie(t);this._lineHeight!==e&&(this._lineHeight=e,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(t){let e=Ie(t);this._letterSpacing!==e&&(this._letterSpacing=e,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(t){this._className!==t&&(this._className=t,this.domNode.className=this._className)}toggleClassName(t,e){this.domNode.classList.toggle(t,e),this._className=this.domNode.className}setDisplay(t){this._display!==t&&(this._display=t,this.domNode.style.display=this._display)}setPosition(t){this._position!==t&&(this._position=t,this.domNode.style.position=this._position)}setVisibility(t){this._visibility!==t&&(this._visibility=t,this.domNode.style.visibility=this._visibility)}setColor(t){this._color!==t&&(this._color=t,this.domNode.style.color=this._color)}setBackgroundColor(t){this._backgroundColor!==t&&(this._backgroundColor=t,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(t){this._layerHint!==t&&(this._layerHint=t,this.domNode.style.transform=this._layerHint?"translate3d(0px, 0px, 0px)":"")}setBoxShadow(t){this._boxShadow!==t&&(this._boxShadow=t,this.domNode.style.boxShadow=t)}setContain(t){this._contain!==t&&(this._contain=t,this.domNode.style.contain=this._contain)}setAttribute(t,e){this.domNode.setAttribute(t,e)}removeAttribute(t){this.domNode.removeAttribute(t)}appendChild(t){this.domNode.appendChild(t.domNode)}removeChild(t){this.domNode.removeChild(t.domNode)}};function Ie(s){return typeof s=="number"?`${s}px`:s}function _t(s){return new ls(s)}var Wt=class{constructor(){this._hooks=new Ee;this._pointerMoveCallback=null;this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(t,e){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let i=this._onStopCallback;this._onStopCallback=null,t&&i&&i(e)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(t,e,i,r,n){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=r,this._onStopCallback=n;let o=t;try{t.setPointerCapture(e),this._hooks.add(C(()=>{try{t.releasePointerCapture(e)}catch{}}))}catch{o=be(t)}this._hooks.add(L(o,Y.POINTER_MOVE,l=>{if(l.buttons!==i){this.stopMonitoring(!0);return}l.preventDefault(),this._pointerMoveCallback(l)})),this._hooks.add(L(o,Y.POINTER_UP,l=>this.stopMonitoring(!0)))}};function Wo(s,t,e){let i=null,r=null;if(typeof e.value=="function"?(i="value",r=e.value,r.length!==0&&console.warn("Memoize should only be used in functions with zero parameters")):typeof e.get=="function"&&(i="get",r=e.get),!r)throw new Error("not supported");let n=`$memoize$${t}`;e[i]=function(...o){return this.hasOwnProperty(n)||Object.defineProperty(this,n,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,o)}),this[n]}}var He;(n=>(n.Tap="-xterm-gesturetap",n.Change="-xterm-gesturechange",n.Start="-xterm-gesturestart",n.End="-xterm-gesturesend",n.Contextmenu="-xterm-gesturecontextmenu"))(He||={});var Q=class Q extends D{constructor(){super();this.dispatched=!1;this.targets=new Ct;this.ignoreTargets=new Ct;this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register($.runAndSubscribe(No,({window:e,disposables:i})=>{i.add(L(e.document,"touchstart",r=>this.onTouchStart(r),{passive:!1})),i.add(L(e.document,"touchend",r=>this.onTouchEnd(e,r))),i.add(L(e.document,"touchmove",r=>this.onTouchMove(r),{passive:!1}))},{window:fe,disposables:this._store}))}static addTarget(e){if(!Q.isTouchDevice())return D.None;Q.INSTANCE||(Q.INSTANCE=Gn(new Q));let i=Q.INSTANCE.targets.push(e);return C(i)}static ignoreTarget(e){if(!Q.isTouchDevice())return D.None;Q.INSTANCE||(Q.INSTANCE=Gn(new Q));let i=Q.INSTANCE.ignoreTargets.push(e);return C(i)}static isTouchDevice(){return"ontouchstart"in fe||navigator.maxTouchPoints>0}dispose(){this.handle&&(this.handle.dispose(),this.handle=null),super.dispose()}onTouchStart(e){let i=Date.now();this.handle&&(this.handle.dispose(),this.handle=null);for(let r=0,n=e.targetTouches.length;r=Q.HOLD_DELAY&&Math.abs(u.initialPageX-Se(u.rollingPageX))<30&&Math.abs(u.initialPageY-Se(u.rollingPageY))<30){let c=this.newGestureEvent(He.Contextmenu,u.initialTarget);c.pageX=Se(u.rollingPageX),c.pageY=Se(u.rollingPageY),this.dispatchEvent(c)}else if(n===1){let c=Se(u.rollingPageX),d=Se(u.rollingPageY),_=Se(u.rollingTimestamps)-u.rollingTimestamps[0],p=c-u.rollingPageX[0],m=d-u.rollingPageY[0],f=[...this.targets].filter(A=>u.initialTarget instanceof Node&&A.contains(u.initialTarget));this.inertia(e,f,r,Math.abs(p)/_,p>0?1:-1,c,Math.abs(m)/_,m>0?1:-1,d)}this.dispatchEvent(this.newGestureEvent(He.End,u.initialTarget)),delete this.activeTouches[a.identifier]}this.dispatched&&(i.preventDefault(),i.stopPropagation(),this.dispatched=!1)}newGestureEvent(e,i){let r=document.createEvent("CustomEvent");return r.initEvent(e,!1,!0),r.initialTarget=i,r.tapCount=0,r}dispatchEvent(e){if(e.type===He.Tap){let i=new Date().getTime(),r=0;i-this._lastSetTapCountTime>Q.CLEAR_TAP_COUNT_TIME?r=1:r=2,this._lastSetTapCountTime=i,e.tapCount=r}else(e.type===He.Change||e.type===He.Contextmenu)&&(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(let r of this.ignoreTargets)if(r.contains(e.initialTarget))return;let i=[];for(let r of this.targets)if(r.contains(e.initialTarget)){let n=0,o=e.initialTarget;for(;o&&o!==r;)n++,o=o.parentElement;i.push([n,r])}i.sort((r,n)=>r[0]-n[0]);for(let[r,n]of i)n.dispatchEvent(e),this.dispatched=!0}}inertia(e,i,r,n,o,l,a,u,h){this.handle=mt(e,()=>{let c=Date.now(),d=c-r,_=0,p=0,m=!0;n+=Q.SCROLL_FRICTION*d,a+=Q.SCROLL_FRICTION*d,n>0&&(m=!1,_=o*n*d),a>0&&(m=!1,p=u*a*d);let f=this.newGestureEvent(He.Change);f.translationX=_,f.translationY=p,i.forEach(A=>A.dispatchEvent(f)),m||this.inertia(e,i,c,n,o,l+_,a,u,h+p)})}onTouchMove(e){let i=Date.now();for(let r=0,n=e.changedTouches.length;r3&&(l.rollingPageX.shift(),l.rollingPageY.shift(),l.rollingTimestamps.shift()),l.rollingPageX.push(o.pageX),l.rollingPageY.push(o.pageY),l.rollingTimestamps.push(i)}this.dispatched&&(e.preventDefault(),e.stopPropagation(),this.dispatched=!1)}};Q.SCROLL_FRICTION=-.005,Q.HOLD_DELAY=700,Q.CLEAR_TAP_COUNT_TIME=400,M([Wo],Q,"isTouchDevice",1);var Pr=Q;var lt=class extends D{onclick(t,e){this._register(L(t,Y.CLICK,i=>e(new qe(be(t),i))))}onmousedown(t,e){this._register(L(t,Y.MOUSE_DOWN,i=>e(new qe(be(t),i))))}onmouseover(t,e){this._register(L(t,Y.MOUSE_OVER,i=>e(new qe(be(t),i))))}onmouseleave(t,e){this._register(L(t,Y.MOUSE_LEAVE,i=>e(new qe(be(t),i))))}onkeydown(t,e){this._register(L(t,Y.KEY_DOWN,i=>e(new ft(i))))}onkeyup(t,e){this._register(L(t,Y.KEY_UP,i=>e(new ft(i))))}oninput(t,e){this._register(L(t,Y.INPUT,e))}onblur(t,e){this._register(L(t,Y.BLUR,e))}onfocus(t,e){this._register(L(t,Y.FOCUS,e))}onchange(t,e){this._register(L(t,Y.CHANGE,e))}ignoreGesture(t){return Pr.ignoreTarget(t)}};var Uo=11,Or=class extends lt{constructor(t){super(),this._onActivate=t.onActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=t.bgWidth+"px",this.bgDomNode.style.height=t.bgHeight+"px",typeof t.top<"u"&&(this.bgDomNode.style.top="0px"),typeof t.left<"u"&&(this.bgDomNode.style.left="0px"),typeof t.bottom<"u"&&(this.bgDomNode.style.bottom="0px"),typeof t.right<"u"&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=t.className,this.domNode.style.position="absolute",this.domNode.style.width=Uo+"px",this.domNode.style.height=Uo+"px",typeof t.top<"u"&&(this.domNode.style.top=t.top+"px"),typeof t.left<"u"&&(this.domNode.style.left=t.left+"px"),typeof t.bottom<"u"&&(this.domNode.style.bottom=t.bottom+"px"),typeof t.right<"u"&&(this.domNode.style.right=t.right+"px"),this._pointerMoveMonitor=this._register(new Wt),this._register(os(this.bgDomNode,Y.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._register(os(this.domNode,Y.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._pointerdownRepeatTimer=this._register(new Mr),this._pointerdownScheduleRepeatTimer=this._register(new Ye)}_arrowPointerDown(t){if(!t.target||!(t.target instanceof Element))return;let e=()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,be(t))};this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(e,200),this._pointerMoveMonitor.startMonitoring(t.target,t.pointerId,t.buttons,i=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),t.preventDefault()}};var cs=class s{constructor(t,e,i,r,n,o,l){this._forceIntegerValues=t;this._scrollStateBrand=void 0;this._forceIntegerValues&&(e=e|0,i=i|0,r=r|0,n=n|0,o=o|0,l=l|0),this.rawScrollLeft=r,this.rawScrollTop=l,e<0&&(e=0),r+e>i&&(r=i-e),r<0&&(r=0),n<0&&(n=0),l+n>o&&(l=o-n),l<0&&(l=0),this.width=e,this.scrollWidth=i,this.scrollLeft=r,this.height=n,this.scrollHeight=o,this.scrollTop=l}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,e){return new s(this._forceIntegerValues,typeof t.width<"u"?t.width:this.width,typeof t.scrollWidth<"u"?t.scrollWidth:this.scrollWidth,e?this.rawScrollLeft:this.scrollLeft,typeof t.height<"u"?t.height:this.height,typeof t.scrollHeight<"u"?t.scrollHeight:this.scrollHeight,e?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new s(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,e){let i=this.width!==t.width,r=this.scrollWidth!==t.scrollWidth,n=this.scrollLeft!==t.scrollLeft,o=this.height!==t.height,l=this.scrollHeight!==t.scrollHeight,a=this.scrollTop!==t.scrollTop;return{inSmoothScrolling:e,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:i,scrollWidthChanged:r,scrollLeftChanged:n,heightChanged:o,scrollHeightChanged:l,scrollTopChanged:a}}},Ri=class extends D{constructor(e){super();this._scrollableBrand=void 0;this._onScroll=this._register(new v);this.onScroll=this._onScroll.event;this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new cs(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,i){let r=this._state.withScrollDimensions(e,i);this._setState(r,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let i=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(i,!1)}setScrollPositionSmooth(e,i){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 r=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===r.scrollLeft&&this._smoothScrolling.to.scrollTop===r.scrollTop)return;let n;i?n=new Nr(this._smoothScrolling.from,r,this._smoothScrolling.startTime,this._smoothScrolling.duration):n=this._smoothScrolling.combine(this._state,r,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=n}else{let r=this._state.withScrollPosition(e);this._smoothScrolling=Nr.start(this._state,r,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(),i=this._state.withScrollPosition(e);if(this._setState(i,!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,i){let r=this._state;r.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(r,i)))}},Br=class{constructor(t,e,i){this.scrollLeft=t,this.scrollTop=e,this.isDone=i}};function as(s,t){let e=t-s;return function(i){return s+e*ka(i)}}function La(s,t,e){return function(i){return i2.5*i){let n,o;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(t){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(t?" fade":"")))}};var Ca=140,Ut=class extends lt{constructor(t){super(),this._lazyRender=t.lazyRender,this._host=t.host,this._scrollable=t.scrollable,this._scrollByPage=t.scrollByPage,this._scrollbarState=t.scrollbarState,this._visibilityController=this._register(new Fr(t.visibility,"visible scrollbar "+t.extraScrollbarClassName,"invisible scrollbar "+t.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new Wt),this._shouldRender=!0,this.domNode=_t(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(L(this.domNode.domNode,Y.POINTER_DOWN,e=>this._domNodePointerDown(e)))}_createArrow(t){let e=this._register(new Or(t));this.domNode.domNode.appendChild(e.bgDomNode),this.domNode.domNode.appendChild(e.domNode)}_createSlider(t,e,i,r){this.slider=_t(document.createElement("div")),this.slider.setClassName("slider"),this.slider.setPosition("absolute"),this.slider.setTop(t),this.slider.setLeft(e),typeof i=="number"&&this.slider.setWidth(i),typeof r=="number"&&this.slider.setHeight(r),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(L(this.slider.domNode,Y.POINTER_DOWN,n=>{n.button===0&&(n.preventDefault(),this._sliderPointerDown(n))})),this.onclick(this.slider.domNode,n=>{n.leftButton&&n.stopPropagation()})}_onElementSize(t){return this._scrollbarState.setVisibleSize(t)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(t){return this._scrollbarState.setScrollSize(t)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(t){return this._scrollbarState.setScrollPosition(t)&&(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(t){t.target===this.domNode.domNode&&this._onPointerDown(t)}delegatePointerDown(t){let e=this.domNode.domNode.getClientRects()[0].top,i=e+this._scrollbarState.getSliderPosition(),r=e+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),n=this._sliderPointerPosition(t);i<=n&&n<=r?t.button===0&&(t.preventDefault(),this._sliderPointerDown(t)):this._onPointerDown(t)}_onPointerDown(t){let e,i;if(t.target===this.domNode.domNode&&typeof t.offsetX=="number"&&typeof t.offsetY=="number")e=t.offsetX,i=t.offsetY;else{let n=Fo(this.domNode.domNode);e=t.pageX-n.left,i=t.pageY-n.top}let r=this._pointerDownRelativePosition(e,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(r):this._scrollbarState.getDesiredScrollPositionFromOffset(r)),t.button===0&&(t.preventDefault(),this._sliderPointerDown(t))}_sliderPointerDown(t){if(!t.target||!(t.target instanceof Element))return;let e=this._sliderPointerPosition(t),i=this._sliderOrthogonalPointerPosition(t),r=this._scrollbarState.clone();this.slider.toggleClassName("active",!0),this._pointerMoveMonitor.startMonitoring(t.target,t.pointerId,t.buttons,n=>{let o=this._sliderOrthogonalPointerPosition(n),l=Math.abs(o-i);if(wr&&l>Ca){this._setDesiredScrollPositionNow(r.getScrollPosition());return}let u=this._sliderPointerPosition(n)-e;this._setDesiredScrollPositionNow(r.getDesiredScrollPositionFromDelta(u))},()=>{this.slider.toggleClassName("active",!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(t){let e={};this.writeScrollPosition(e,t),this._scrollable.setScrollPositionNow(e)}updateScrollbarSize(t){this._updateScrollbarSize(t),this._scrollbarState.setScrollbarSize(t),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}};var Kt=class s{constructor(t,e,i,r,n,o){this._scrollbarSize=Math.round(e),this._oppositeScrollbarSize=Math.round(i),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 s(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(t){let e=Math.round(t);return this._visibleSize!==e?(this._visibleSize=e,this._refreshComputedValues(),!0):!1}setScrollSize(t){let e=Math.round(t);return this._scrollSize!==e?(this._scrollSize=e,this._refreshComputedValues(),!0):!1}setScrollPosition(t){let e=Math.round(t);return this._scrollPosition!==e?(this._scrollPosition=e,this._refreshComputedValues(),!0):!1}setScrollbarSize(t){this._scrollbarSize=Math.round(t)}setOppositeScrollbarSize(t){this._oppositeScrollbarSize=Math.round(t)}static _computeValues(t,e,i,r,n){let o=Math.max(0,i-t),l=Math.max(0,o-2*e),a=r>0&&r>i;if(!a)return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(l),computedSliderRatio:0,computedSliderPosition:0};let u=Math.round(Math.max(20,Math.floor(i*l/r))),h=(l-u)/(r-i),c=n*h;return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(u),computedSliderRatio:h,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){let t=s._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 e=t-this._arrowSize-this._computedSliderSize/2;return Math.round(e/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(t){if(!this._computedIsNeeded)return 0;let e=t-this._arrowSize,i=this._scrollPosition;return e0&&Math.abs(t.deltaY)>0)return 1;let i=.5;if((!this._isAlmostInt(t.deltaX)||!this._isAlmostInt(t.deltaY))&&(i+=.25),e){let r=Math.abs(t.deltaX),n=Math.abs(t.deltaY),o=Math.abs(e.deltaX),l=Math.abs(e.deltaY),a=Math.max(Math.min(r,o),1),u=Math.max(Math.min(n,l),1),h=Math.max(r,o),c=Math.max(n,l);h%a===0&&c%u===0&&(i-=.5)}return Math.min(Math.max(i,0),1)}_isAlmostInt(t){return Math.abs(Math.round(t)-t)<.01}};zr.INSTANCE=new zr;var hs=zr,ds=class extends lt{constructor(e,i,r){super();this._onScroll=this._register(new v);this.onScroll=this._onScroll.event;this._onWillScroll=this._register(new v);this.onWillScroll=this._onWillScroll.event;this._options=Pa(i),this._scrollable=r,this._register(this._scrollable.onScroll(o=>{this._onWillScroll.fire(o),this._onDidScroll(o),this._onScroll.fire(o)}));let n={onMouseWheel:o=>this._onMouseWheel(o),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new Ur(this._scrollable,this._options,n)),this._horizontalScrollbar=this._register(new Wr(this._scrollable,this._options,n)),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=_t(document.createElement("div")),this._leftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=_t(document.createElement("div")),this._topShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=_t(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,o=>this._onMouseOver(o)),this.onmouseleave(this._listenOnDomNode,o=>this._onMouseLeave(o)),this._hideTimeout=this._register(new Ye),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=Ne(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,Te&&(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 xi(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=Ne(this._mouseWheelToDispose),e)){let r=n=>{this._onMouseWheel(new xi(n))};this._mouseWheelToDispose.push(L(this._listenOnDomNode,Y.MOUSE_WHEEL,r,{passive:!1}))}}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let i=hs.INSTANCE;zo&&i.acceptStandardWheelEvent(e);let r=!1;if(e.deltaY||e.deltaX){let o=e.deltaY*this._options.mouseWheelScrollSensitivity,l=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&l+o===0?l=o=0:Math.abs(o)>=Math.abs(l)?l=0:o=0),this._options.flipAxes&&([o,l]=[l,o]);let a=!Te&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||a)&&!l&&(l=o,o=0),e.browserEvent&&e.browserEvent.altKey&&(l=l*this._options.fastScrollSensitivity,o=o*this._options.fastScrollSensitivity);let u=this._scrollable.getFutureScrollPosition(),h={};if(o){let c=Ko*o,d=u.scrollTop-(c<0?Math.floor(c):Math.ceil(c));this._verticalScrollbar.writeScrollPosition(h,d)}if(l){let c=Ko*l,d=u.scrollLeft-(c<0?Math.floor(c):Math.ceil(c));this._horizontalScrollbar.writeScrollPosition(h,d)}h=this._scrollable.validateScrollPosition(h),(u.scrollLeft!==h.scrollLeft||u.scrollTop!==h.scrollTop)&&(zo&&this._options.mouseWheelSmoothScroll&&i.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(h):this._scrollable.setScrollPositionNow(h),r=!0)}let n=r;!n&&this._options.alwaysConsumeMouseWheel&&(n=!0),!n&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(n=!0),n&&(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(),i=e.scrollTop>0,r=e.scrollLeft>0,n=r?" left":"",o=i?" top":"",l=r||i?" top-left-corner":"";this._leftShadowDomNode.setClassName(`shadow${n}`),this._topShadowDomNode.setClassName(`shadow${o}`),this._topLeftShadowDomNode.setClassName(`shadow${l}${o}${n}`)}}_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(),Ma)}};var Kr=class extends ds{constructor(t,e,i){super(t,e,i)}setScrollPosition(t){t.reuseAnimation?this._scrollable.setScrollPositionSmooth(t,t.reuseAnimation):this._scrollable.setScrollPositionNow(t)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function Pa(s){let t={lazyRender:typeof s.lazyRender<"u"?s.lazyRender:!1,className:typeof s.className<"u"?s.className:"",useShadows:typeof s.useShadows<"u"?s.useShadows:!0,handleMouseWheel:typeof s.handleMouseWheel<"u"?s.handleMouseWheel:!0,flipAxes:typeof s.flipAxes<"u"?s.flipAxes:!1,consumeMouseWheelIfScrollbarIsNeeded:typeof s.consumeMouseWheelIfScrollbarIsNeeded<"u"?s.consumeMouseWheelIfScrollbarIsNeeded:!1,alwaysConsumeMouseWheel:typeof s.alwaysConsumeMouseWheel<"u"?s.alwaysConsumeMouseWheel:!1,scrollYToX:typeof s.scrollYToX<"u"?s.scrollYToX:!1,mouseWheelScrollSensitivity:typeof s.mouseWheelScrollSensitivity<"u"?s.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof s.fastScrollSensitivity<"u"?s.fastScrollSensitivity:5,scrollPredominantAxis:typeof s.scrollPredominantAxis<"u"?s.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof s.mouseWheelSmoothScroll<"u"?s.mouseWheelSmoothScroll:!0,arrowSize:typeof s.arrowSize<"u"?s.arrowSize:11,listenOnDomNode:typeof s.listenOnDomNode<"u"?s.listenOnDomNode:null,horizontal:typeof s.horizontal<"u"?s.horizontal:1,horizontalScrollbarSize:typeof s.horizontalScrollbarSize<"u"?s.horizontalScrollbarSize:10,horizontalSliderSize:typeof s.horizontalSliderSize<"u"?s.horizontalSliderSize:0,horizontalHasArrows:typeof s.horizontalHasArrows<"u"?s.horizontalHasArrows:!1,vertical:typeof s.vertical<"u"?s.vertical:1,verticalScrollbarSize:typeof s.verticalScrollbarSize<"u"?s.verticalScrollbarSize:10,verticalHasArrows:typeof s.verticalHasArrows<"u"?s.verticalHasArrows:!1,verticalSliderSize:typeof s.verticalSliderSize<"u"?s.verticalSliderSize:0,scrollByPage:typeof s.scrollByPage<"u"?s.scrollByPage:!1};return t.horizontalSliderSize=typeof s.horizontalSliderSize<"u"?s.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof s.verticalSliderSize<"u"?s.verticalSliderSize:t.verticalScrollbarSize,Te&&(t.className+=" mac"),t}var zt=class extends D{constructor(e,i,r,n,o,l,a,u){super();this._bufferService=r;this._optionsService=a;this._renderService=u;this._onRequestScrollLines=this._register(new v);this.onRequestScrollLines=this._onRequestScrollLines.event;this._isSyncing=!1;this._isHandlingScroll=!1;this._suppressOnScrollHandler=!1;let h=this._register(new Ri({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:c=>mt(n.window,c)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{h.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new Kr(i,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},h)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","overviewRuler"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(o.onProtocolChange(c=>{this._scrollableElement.updateOptions({handleMouseWheel:!(c&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register($.runAndSubscribe(l.onChangeColors,()=>{this._scrollableElement.getDomNode().style.backgroundColor=l.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(C(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=n.mainDocument.createElement("style"),i.appendChild(this._styleElement),this._register(C(()=>this._styleElement.remove())),this._register($.runAndSubscribe(l.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .scrollbar > .slider {",` background: ${l.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider:hover {",` background: ${l.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider.active {",` background: ${l.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(c=>this._handleScroll(c)))}scrollLines(e){let i=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:i.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,i){i&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!i,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 i=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),r=i-this._bufferService.buffer.ydisp;r!==0&&(this._latestYDisp=i,this._onRequestScrollLines.fire(r)),this._isHandlingScroll=!1}};zt=M([S(2,F),S(3,ae),S(4,rr),S(5,Re),S(6,H),S(7,ce)],zt);var Gt=class extends D{constructor(e,i,r,n,o){super();this._screenElement=e;this._bufferService=i;this._coreBrowserService=r;this._decorationService=n;this._renderService=o;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(l=>this._removeDecoration(l))),this._register(C(()=>{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 i=this._coreBrowserService.mainDocument.createElement("div");i.classList.add("xterm-decoration"),i.classList.toggle("xterm-decoration-top-layer",e?.options?.layer==="top"),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=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let r=e.options.x??0;return r&&r>this._bufferService.cols&&(i.style.display="none"),this._refreshXPosition(e,i),i}_refreshStyle(e){let i=e.marker.line-this._bufferService.buffers.active.ydisp;if(i<0||i>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let r=this._decorationElements.get(e);r||(r=this._createElement(e),e.element=r,this._decorationElements.set(e,r),this._container.appendChild(r),e.onDispose(()=>{this._decorationElements.delete(e),r.remove()})),r.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(r.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,r.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,r.style.top=`${i*this._renderService.dimensions.css.cell.height}px`,r.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(r)}}_refreshXPosition(e,i=e.element){if(!i)return;let r=e.options.x??0;(e.options.anchor||"left")==="right"?i.style.right=r?`${r*this._renderService.dimensions.css.cell.width}px`:"":i.style.left=r?`${r*this._renderService.dimensions.css.cell.width}px`:""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};Gt=M([S(1,F),S(2,ae),S(3,Be),S(4,ce)],Gt);var Gr=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(t){if(t.options.overviewRulerOptions){for(let e of this._zones)if(e.color===t.options.overviewRulerOptions.color&&e.position===t.options.overviewRulerOptions.position){if(this._lineIntersectsZone(e,t.marker.line))return;if(this._lineAdjacentToZone(e,t.marker.line,t.options.overviewRulerOptions.position)){this._addLineToZone(e,t.marker.line);return}}if(this._zonePoolIndex=t.startBufferLine&&e<=t.endBufferLine}_lineAdjacentToZone(t,e,i){return e>=t.startBufferLine-this._linePadding[i||"full"]&&e<=t.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(t,e){t.startBufferLine=Math.min(t.startBufferLine,e),t.endBufferLine=Math.max(t.endBufferLine,e)}};var We={full:0,left:0,center:0,right:0},at={full:0,left:0,center:0,right:0},Li={full:0,left:0,center:0,right:0},bt=class extends D{constructor(e,i,r,n,o,l,a,u){super();this._viewportElement=e;this._screenElement=i;this._bufferService=r;this._decorationService=n;this._renderService=o;this._optionsService=l;this._themeService=a;this._coreBrowserService=u;this._colorZoneStore=new Gr;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(C(()=>this._canvas?.remove()));let h=this._canvas.getContext("2d");if(h)this._ctx=h;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),i=Math.ceil((this._canvas.width-1)/3);at.full=this._canvas.width,at.left=e,at.center=i,at.right=e,this._refreshDrawHeightConstants(),Li.full=1,Li.left=1,Li.center=1+at.left,Li.right=1+at.left+at.center}_refreshDrawHeightConstants(){We.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,i=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);We.left=i,We.center=i,We.right=i}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*We.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*We.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*We.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*We.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 i of this._decorationService.decorations)this._colorZoneStore.addDecoration(i);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let i of e)i.position!=="full"&&this._renderColorZone(i);for(let i of e)i.position==="full"&&this._renderColorZone(i);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(Li[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-We[e.position||"full"]/2),at[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+We[e.position||"full"]))}_queueRefresh(e,i){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=i||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};bt=M([S(2,F),S(3,Be),S(4,ce),S(5,H),S(6,Re),S(7,ae)],bt);var b;(E=>(E.NUL="\0",E.SOH="",E.STX="",E.ETX="",E.EOT="",E.ENQ="",E.ACK="",E.BEL="\x07",E.BS="\b",E.HT=" ",E.LF=` +`,E.VT="\v",E.FF="\f",E.CR="\r",E.SO="",E.SI="",E.DLE="",E.DC1="",E.DC2="",E.DC3="",E.DC4="",E.NAK="",E.SYN="",E.ETB="",E.CAN="",E.EM="",E.SUB="",E.ESC="\x1B",E.FS="",E.GS="",E.RS="",E.US="",E.SP=" ",E.DEL="\x7F"))(b||={});var Ai;(g=>(g.PAD="\x80",g.HOP="\x81",g.BPH="\x82",g.NBH="\x83",g.IND="\x84",g.NEL="\x85",g.SSA="\x86",g.ESA="\x87",g.HTS="\x88",g.HTJ="\x89",g.VTS="\x8A",g.PLD="\x8B",g.PLU="\x8C",g.RI="\x8D",g.SS2="\x8E",g.SS3="\x8F",g.DCS="\x90",g.PU1="\x91",g.PU2="\x92",g.STS="\x93",g.CCH="\x94",g.MW="\x95",g.SPA="\x96",g.EPA="\x97",g.SOS="\x98",g.SGCI="\x99",g.SCI="\x9A",g.CSI="\x9B",g.ST="\x9C",g.OSC="\x9D",g.PM="\x9E",g.APC="\x9F"))(Ai||={});var fs;(t=>t.ST=`${b.ESC}\\`)(fs||={});var $t=class{constructor(t,e,i,r,n,o){this._textarea=t;this._compositionView=e;this._bufferService=i;this._optionsService=r;this._coreService=n;this._renderService=o;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(t){this._compositionView.textContent=t.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(t){if(this._isComposing||this._isSendingComposition){if(t.keyCode===20||t.keyCode===229||t.keyCode===16||t.keyCode===17||t.keyCode===18)return!1;this._finalizeComposition(!1)}return t.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(t){if(this._compositionView.classList.remove("active"),this._isComposing=!1,t){let e={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let i;e.start+=this._dataAlreadySent.length,this._isComposing?i=this._textarea.value.substring(e.start,this._compositionPosition.start):i=this._textarea.value.substring(e.start),i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;let e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){let t=this._textarea.value;setTimeout(()=>{if(!this._isComposing){let e=this._textarea.value,i=e.replace(t,"");this._dataAlreadySent=i,e.length>t.length?this._coreService.triggerDataEvent(i,!0):e.lengththis.updateCompositionElements(!0),0)}}};$t=M([S(2,F),S(3,H),S(4,ge),S(5,ce)],$t);var ue=0,he=0,de=0,J=0,ps={css:"#00000000",rgba:0},j;(i=>{function s(r,n,o,l){return l!==void 0?`#${vt(r)}${vt(n)}${vt(o)}${vt(l)}`:`#${vt(r)}${vt(n)}${vt(o)}`}i.toCss=s;function t(r,n,o,l=255){return(r<<24|n<<16|o<<8|l)>>>0}i.toRgba=t;function e(r,n,o,l){return{css:i.toCss(r,n,o,l),rgba:i.toRgba(r,n,o,l)}}i.toColor=e})(j||={});var U;(l=>{function s(a,u){if(J=(u.rgba&255)/255,J===1)return{css:u.css,rgba:u.rgba};let h=u.rgba>>24&255,c=u.rgba>>16&255,d=u.rgba>>8&255,_=a.rgba>>24&255,p=a.rgba>>16&255,m=a.rgba>>8&255;ue=_+Math.round((h-_)*J),he=p+Math.round((c-p)*J),de=m+Math.round((d-m)*J);let f=j.toCss(ue,he,de),A=j.toRgba(ue,he,de);return{css:f,rgba:A}}l.blend=s;function t(a){return(a.rgba&255)===255}l.isOpaque=t;function e(a,u,h){let c=$r.ensureContrastRatio(a.rgba,u.rgba,h);if(c)return j.toColor(c>>24&255,c>>16&255,c>>8&255)}l.ensureContrastRatio=e;function i(a){let u=(a.rgba|255)>>>0;return[ue,he,de]=$r.toChannels(u),{css:j.toCss(ue,he,de),rgba:u}}l.opaque=i;function r(a,u){return J=Math.round(u*255),[ue,he,de]=$r.toChannels(a.rgba),{css:j.toCss(ue,he,de,J),rgba:j.toRgba(ue,he,de,J)}}l.opacity=r;function n(a,u){return J=a.rgba&255,r(a,J*u/255)}l.multiplyOpacity=n;function o(a){return[a.rgba>>24&255,a.rgba>>16&255,a.rgba>>8&255]}l.toColorRGB=o})(U||={});var z;(i=>{let s,t;try{let r=document.createElement("canvas");r.width=1,r.height=1;let n=r.getContext("2d",{willReadFrequently:!0});n&&(s=n,s.globalCompositeOperation="copy",t=s.createLinearGradient(0,0,1,1))}catch{}function e(r){if(r.match(/#[\da-f]{3,8}/i))switch(r.length){case 4:return ue=parseInt(r.slice(1,2).repeat(2),16),he=parseInt(r.slice(2,3).repeat(2),16),de=parseInt(r.slice(3,4).repeat(2),16),j.toColor(ue,he,de);case 5:return ue=parseInt(r.slice(1,2).repeat(2),16),he=parseInt(r.slice(2,3).repeat(2),16),de=parseInt(r.slice(3,4).repeat(2),16),J=parseInt(r.slice(4,5).repeat(2),16),j.toColor(ue,he,de,J);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 ue=parseInt(n[1]),he=parseInt(n[2]),de=parseInt(n[3]),J=Math.round((n[5]===void 0?1:parseFloat(n[5]))*255),j.toColor(ue,he,de,J);if(!s||!t)throw new Error("css.toColor: Unsupported css format");if(s.fillStyle=t,s.fillStyle=r,typeof s.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(s.fillRect(0,0,1,1),[ue,he,de,J]=s.getImageData(0,0,1,1).data,J!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:j.toRgba(ue,he,de,J),css:r}}i.toColor=e})(z||={});var ve;(e=>{function s(i){return t(i>>16&255,i>>8&255,i&255)}e.relativeLuminance=s;function t(i,r,n){let o=i/255,l=r/255,a=n/255,u=o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4),h=l<=.03928?l/12.92:Math.pow((l+.055)/1.055,2.4),c=a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4);return u*.2126+h*.7152+c*.0722}e.relativeLuminance2=t})(ve||={});var $r;(n=>{function s(o,l){if(J=(l&255)/255,J===1)return l;let a=l>>24&255,u=l>>16&255,h=l>>8&255,c=o>>24&255,d=o>>16&255,_=o>>8&255;return ue=c+Math.round((a-c)*J),he=d+Math.round((u-d)*J),de=_+Math.round((h-_)*J),j.toRgba(ue,he,de)}n.blend=s;function t(o,l,a){let u=ve.relativeLuminance(o>>8),h=ve.relativeLuminance(l>>8);if(Xe(u,h)>8));if(m>8));return m>A?p:f}return p}let d=i(o,l,a),_=Xe(u,ve.relativeLuminance(d>>8));if(_>8));return _>m?d:p}return d}}n.ensureContrastRatio=t;function e(o,l,a){let u=o>>24&255,h=o>>16&255,c=o>>8&255,d=l>>24&255,_=l>>16&255,p=l>>8&255,m=Xe(ve.relativeLuminance2(d,_,p),ve.relativeLuminance2(u,h,c));for(;m0||_>0||p>0);)d-=Math.max(0,Math.ceil(d*.1)),_-=Math.max(0,Math.ceil(_*.1)),p-=Math.max(0,Math.ceil(p*.1)),m=Xe(ve.relativeLuminance2(d,_,p),ve.relativeLuminance2(u,h,c));return(d<<24|_<<16|p<<8|255)>>>0}n.reduceLuminance=e;function i(o,l,a){let u=o>>24&255,h=o>>16&255,c=o>>8&255,d=l>>24&255,_=l>>16&255,p=l>>8&255,m=Xe(ve.relativeLuminance2(d,_,p),ve.relativeLuminance2(u,h,c));for(;m>>0}n.increaseLuminance=i;function r(o){return[o>>24&255,o>>16&255,o>>8&255,o&255]}n.toChannels=r})($r||={});function vt(s){let t=s.toString(16);return t.length<2?"0"+t:t}function Xe(s,t){return s1){let c=this._getJoinedRanges(r,l,o,e,n);for(let d=0;d1){let h=this._getJoinedRanges(r,l,o,e,n);for(let c=0;c=di,E=y,x=this._workCell;if(_.length>0&&y===_[0][0]&&w){let W=_.shift(),An=this._isCellInSelection(W[0],e);for(O=W[0]+1;O=W[1],w?(g=!0,x=new Vr(this._workCell,t.translateToString(!0,W[0],W[1]),W[1]-W[0]),E=W[1]-1,T=x.getWidth()):di=W[1]}let N=this._isCellInSelection(y,e),Z=i&&y===o,te=Qe&&y>=h&&y<=c,Oe=!1;this._decorationService.forEachDecorationAtCell(y,e,void 0,W=>{Oe=!0});let ze=x.getChars()||we;if(ze===" "&&(x.isUnderline()||x.isOverline())&&(ze="\xA0"),Ke=T*a-u.get(ze,x.isBold(),x.isItalic()),!f)f=this._document.createElement("span");else if(A&&(N&&Pe||!N&&!Pe&&x.bg===I)&&(N&&Pe&&p.selectionForeground||x.fg===k)&&x.extended.ext===P&&te===oe&&Ke===Me&&!Z&&!g&&!Oe&&w){x.isInvisible()?R+=we:R+=ze,A++;continue}else A&&(f.textContent=R),f=this._document.createElement("span"),A=0,R="";if(I=x.bg,k=x.fg,P=x.extended.ext,oe=te,Me=Ke,Pe=N,g&&o>=y&&o<=E&&(o=y),!this._coreService.isCursorHidden&&Z&&this._coreService.isCursorInitialized){if(V.push("xterm-cursor"),this._coreBrowserService.isFocused)l&&V.push("xterm-cursor-blink"),V.push(r==="bar"?"xterm-cursor-bar":r==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(n)switch(n){case"outline":V.push("xterm-cursor-outline");break;case"block":V.push("xterm-cursor-block");break;case"bar":V.push("xterm-cursor-bar");break;case"underline":V.push("xterm-cursor-underline");break;default:break}}if(x.isBold()&&V.push("xterm-bold"),x.isItalic()&&V.push("xterm-italic"),x.isDim()&&V.push("xterm-dim"),x.isInvisible()?R=we:R=x.getChars()||we,x.isUnderline()&&(V.push(`xterm-underline-${x.extended.underlineStyle}`),R===" "&&(R="\xA0"),!x.isUnderlineColorDefault()))if(x.isUnderlineColorRGB())f.style.textDecorationColor=`rgb(${De.toColorRGB(x.getUnderlineColor()).join(",")})`;else{let W=x.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&x.isBold()&&W<8&&(W+=8),f.style.textDecorationColor=p.ansi[W].css}x.isOverline()&&(V.push("xterm-overline"),R===" "&&(R="\xA0")),x.isStrikethrough()&&V.push("xterm-strikethrough"),te&&(f.style.textDecoration="underline");let le=x.getFgColor(),et=x.getFgColorMode(),me=x.getBgColor(),ht=x.getBgColorMode(),fi=!!x.isInverse();if(fi){let W=le;le=me,me=W;let An=et;et=ht,ht=An}let tt,Qi,pi=!1;this._decorationService.forEachDecorationAtCell(y,e,void 0,W=>{W.options.layer!=="top"&&pi||(W.backgroundColorRGB&&(ht=50331648,me=W.backgroundColorRGB.rgba>>8&16777215,tt=W.backgroundColorRGB),W.foregroundColorRGB&&(et=50331648,le=W.foregroundColorRGB.rgba>>8&16777215,Qi=W.foregroundColorRGB),pi=W.options.layer==="top")}),!pi&&N&&(tt=this._coreBrowserService.isFocused?p.selectionBackgroundOpaque:p.selectionInactiveBackgroundOpaque,me=tt.rgba>>8&16777215,ht=50331648,pi=!0,p.selectionForeground&&(et=50331648,le=p.selectionForeground.rgba>>8&16777215,Qi=p.selectionForeground)),pi&&V.push("xterm-decoration-top");let it;switch(ht){case 16777216:case 33554432:it=p.ansi[me],V.push(`xterm-bg-${me}`);break;case 50331648:it=j.toColor(me>>16,me>>8&255,me&255),this._addStyle(f,`background-color:#${qo((me>>>0).toString(16),"0",6)}`);break;case 0:default:fi?(it=p.foreground,V.push(`xterm-bg-${257}`)):it=p.background}switch(tt||x.isDim()&&(tt=U.multiplyOpacity(it,.5)),et){case 16777216:case 33554432:x.isBold()&&le<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(le+=8),this._applyMinimumContrast(f,it,p.ansi[le],x,tt,void 0)||V.push(`xterm-fg-${le}`);break;case 50331648:let W=j.toColor(le>>16&255,le>>8&255,le&255);this._applyMinimumContrast(f,it,W,x,tt,Qi)||this._addStyle(f,`color:#${qo(le.toString(16),"0",6)}`);break;case 0:default:this._applyMinimumContrast(f,it,p.foreground,x,tt,Qi)||fi&&V.push(`xterm-fg-${257}`)}V.length&&(f.className=V.join(" "),V.length=0),!Z&&!g&&!Oe&&w?A++:f.textContent=R,Ke!==this.defaultSpacing&&(f.style.letterSpacing=`${Ke}px`),d.push(f),y=E}return f&&A&&(f.textContent=R),d}_applyMinimumContrast(t,e,i,r,n,o){if(this._optionsService.rawOptions.minimumContrastRatio===1||$o(r.getCode()))return!1;let l=this._getContrastCache(r),a;if(!n&&!o&&(a=l.getColor(e.rgba,i.rgba)),a===void 0){let u=this._optionsService.rawOptions.minimumContrastRatio/(r.isDim()?2:1);a=U.ensureContrastRatio(n||e,o||i,u),l.setColor((n||e).rgba,(o||i).rgba,a??null)}return a?(this._addStyle(t,`color:${a.css}`),!0):!1}_getContrastCache(t){return t.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(t,e){t.setAttribute("style",`${t.getAttribute("style")||""}${e};`)}_isCellInSelection(t,e){let i=this._selectionStart,r=this._selectionEnd;return!i||!r?!1:this._columnSelectMode?i[0]<=r[0]?t>=i[0]&&e>=i[1]&&t=i[1]&&t>=r[0]&&e<=r[1]:e>i[1]&&e=i[0]&&t=i[0]}};Vt=M([S(1,or),S(2,H),S(3,ae),S(4,ge),S(5,Be),S(6,Re)],Vt);function qo(s,t,e){for(;s.length0&&(this._flat[r]=l),l}let n=t;e&&(n+="B"),i&&(n+="I");let o=this._holey.get(n);if(o===void 0){let l=0;e&&(l|=1),i&&(l|=2),o=this._measure(t,l),o>0&&this._holey.set(n,o)}return o}_measure(t,e){let i=this._measureElements[e];return i.textContent=t.repeat(32),i.offsetWidth/32}};var ms=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(t,e,i,r=!1){if(this.selectionStart=e,this.selectionEnd=i,!e||!i||e[0]===i[0]&&e[1]===i[1]){this.clear();return}let n=t.buffers.active.ydisp,o=e[1]-n,l=i[1]-n,a=Math.max(o,0),u=Math.min(l,t.rows-1);if(a>=t.rows||u<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=r,this.viewportStartRow=o,this.viewportEndRow=l,this.viewportCappedStartRow=a,this.viewportCappedEndRow=u,this.startCol=e[0],this.endCol=i[0]}isCellSelected(t,e,i){return this.hasSelection?(i-=t.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?e>=this.startCol&&i>=this.viewportCappedStartRow&&e=this.viewportCappedStartRow&&e>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&e=this.startCol):!1}};function Yo(){return new ms}var _s="xterm-dom-renderer-owner-",Le="xterm-rows",jr="xterm-fg-",jo="xterm-bg-",ki="xterm-focus",Xr="xterm-selection",Na=1,Yt=class extends D{constructor(e,i,r,n,o,l,a,u,h,c,d,_,p,m){super();this._terminal=e;this._document=i;this._element=r;this._screenElement=n;this._viewportElement=o;this._helperContainer=l;this._linkifier2=a;this._charSizeService=h;this._optionsService=c;this._bufferService=d;this._coreService=_;this._coreBrowserService=p;this._themeService=m;this._terminalClass=Na++;this._rowElements=[];this._selectionRenderModel=Yo();this.onRequestRedraw=this._register(new v).event;this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(Le),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(Xr),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=Vo(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(f=>this._injectCss(f))),this._injectCss(this._themeService.colors),this._rowFactory=u.createInstance(Vt,document),this._element.classList.add(_s+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(f=>this._handleLinkHover(f))),this._register(this._linkifier2.onHideLinkUnderline(f=>this._handleLinkLeave(f))),this._register(C(()=>{this._element.classList.remove(_s+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new Yr(this._document,this._helperContainer),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 r of this._rowElements)r.style.width=`${this.dimensions.css.canvas.width}px`,r.style.height=`${this.dimensions.css.cell.height}px`,r.style.lineHeight=`${this.dimensions.css.cell.height}px`,r.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));let i=`${this._terminalSelector} .${Le} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=i,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 i=`${this._terminalSelector} .${Le} { 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}`;i+=`${this._terminalSelector} .${Le} .xterm-dim { color: ${U.multiplyOpacity(e.foreground,.5).css};}`,i+=`${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 r=`blink_underline_${this._terminalClass}`,n=`blink_bar_${this._terminalClass}`,o=`blink_block_${this._terminalClass}`;i+=`@keyframes ${r} { 50% { border-bottom-style: hidden; }}`,i+=`@keyframes ${n} { 50% { box-shadow: none; }}`,i+=`@keyframes ${o} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,i+=`${this._terminalSelector} .${Le}.${ki} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${Le}.${ki} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${n} 1s step-end infinite;}${this._terminalSelector} .${Le}.${ki} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${o} 1s step-end infinite;}${this._terminalSelector} .${Le} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${Le} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${Le} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${Le} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${Le} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,i+=`${this._terminalSelector} .${Xr} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Xr} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Xr} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[l,a]of e.ansi.entries())i+=`${this._terminalSelector} .${jr}${l} { color: ${a.css}; }${this._terminalSelector} .${jr}${l}.xterm-dim { color: ${U.multiplyOpacity(a,.5).css}; }${this._terminalSelector} .${jo}${l} { background-color: ${a.css}; }`;i+=`${this._terminalSelector} .${jr}${257} { color: ${U.opaque(e.background).css}; }${this._terminalSelector} .${jr}${257}.xterm-dim { color: ${U.multiplyOpacity(U.opaque(e.background),.5).css}; }${this._terminalSelector} .${jo}${257} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=i}_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,i){for(let r=this._rowElements.length;r<=i;r++){let n=this._document.createElement("div");this._rowContainer.appendChild(n),this._rowElements.push(n)}for(;this._rowElements.length>i;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,i){this._refreshRowElements(e,i),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(ki),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(ki),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,i,r){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,i,r),this.renderRows(0,this._bufferService.rows-1),!e||!i||(this._selectionRenderModel.update(this._terminal,e,i,r),!this._selectionRenderModel.hasSelection))return;let n=this._selectionRenderModel.viewportStartRow,o=this._selectionRenderModel.viewportEndRow,l=this._selectionRenderModel.viewportCappedStartRow,a=this._selectionRenderModel.viewportCappedEndRow,u=this._document.createDocumentFragment();if(r){let h=e[0]>i[0];u.appendChild(this._createSelectionElement(l,h?i[0]:e[0],h?e[0]:i[0],a-l+1))}else{let h=n===l?e[0]:0,c=l===o?i[0]:this._bufferService.cols;u.appendChild(this._createSelectionElement(l,h,c));let d=a-l-1;if(u.appendChild(this._createSelectionElement(l+1,0,this._bufferService.cols,d)),l!==a){let _=o===a?i[0]:this._bufferService.cols;u.appendChild(this._createSelectionElement(a,0,_))}}this._selectionContainer.appendChild(u)}_createSelectionElement(e,i,r,n=1){let o=this._document.createElement("div"),l=i*this.dimensions.css.cell.width,a=this.dimensions.css.cell.width*(r-i);return l+a>this.dimensions.css.canvas.width&&(a=this.dimensions.css.canvas.width-l),o.style.height=`${n*this.dimensions.css.cell.height}px`,o.style.top=`${e*this.dimensions.css.cell.height}px`,o.style.left=`${l}px`,o.style.width=`${a}px`,o}handleCursorMove(){}_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,i){let r=this._bufferService.buffer,n=r.ybase+r.y,o=Math.min(r.x,this._bufferService.cols-1),l=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,a=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,u=this._optionsService.rawOptions.cursorInactiveStyle;for(let h=e;h<=i;h++){let c=h+r.ydisp,d=this._rowElements[h],_=r.lines.get(c);if(!d||!_)break;d.replaceChildren(...this._rowFactory.createRow(_,c,c===n,a,u,o,l,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${_s}${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,i,r,n,o,l){r<0&&(e=0),n<0&&(i=0);let a=this._bufferService.rows-1;r=Math.max(Math.min(r,a),0),n=Math.max(Math.min(n,a),0),o=Math.min(o,this._bufferService.cols);let u=this._bufferService.buffer,h=u.ybase+u.y,c=Math.min(u.x,o-1),d=this._optionsService.rawOptions.cursorBlink,_=this._optionsService.rawOptions.cursorStyle,p=this._optionsService.rawOptions.cursorInactiveStyle;for(let m=r;m<=n;++m){let f=m+u.ydisp,A=this._rowElements[m],R=u.lines.get(f);if(!A||!R)break;A.replaceChildren(...this._rowFactory.createRow(R,f,f===h,_,p,c,d,this.dimensions.css.cell.width,this._widthCache,l?m===r?e:0:-1,l?(m===n?i:o)-1:-1))}}};Yt=M([S(7,xt),S(8,nt),S(9,H),S(10,F),S(11,ge),S(12,ae),S(13,Re)],Yt);var jt=class extends D{constructor(e,i,r){super();this._optionsService=r;this.width=0;this.height=0;this._onCharSizeChange=this._register(new v);this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new vs(this._optionsService))}catch{this._measureStrategy=this._register(new bs(e,i,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())}};jt=M([S(2,H)],jt);var Zr=class extends D{constructor(){super(...arguments);this._result={width:0,height:0}}_validateAndSet(e,i){e!==void 0&&e>0&&i!==void 0&&i>0&&(this._result.width=e,this._result.height=i)}},bs=class extends Zr{constructor(e,i,r){super();this._document=e;this._parentElement=i;this._optionsService=r;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}},vs=class extends Zr{constructor(e){super();this._optionsService=e;this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");let i=this._ctx.measureText("W");if(!("width"in i&&"fontBoundingBoxAscent"in i&&"fontBoundingBoxDescent"in i))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}};var Jr=class extends D{constructor(e,i,r){super();this._textarea=e;this._window=i;this.mainDocument=r;this._isFocused=!1;this._cachedIsFocused=void 0;this._screenDprMonitor=this._register(new gs(this._window));this._onDprChange=this._register(new v);this.onDprChange=this._onDprChange.event;this._onWindowChange=this._register(new v);this.onWindowChange=this._onWindowChange.event;this._register(this.onWindowChange(n=>this._screenDprMonitor.setWindow(n))),this._register($.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(L(this._textarea,"focus",()=>this._isFocused=!0)),this._register(L(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}},gs=class extends D{constructor(e){super();this._parentWindow=e;this._windowResizeListener=this._register(new ye);this._onDprChange=this._register(new v);this.onDprChange=this._onDprChange.event;this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(C(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=L(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)}};var Qr=class extends D{constructor(){super();this.linkProviders=[];this._register(C(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let i=this.linkProviders.indexOf(e);i!==-1&&this.linkProviders.splice(i,1)}}}};function Ci(s,t,e){let i=e.getBoundingClientRect(),r=s.getComputedStyle(e),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-i.left-n,t.clientY-i.top-o]}function Xo(s,t,e,i,r,n,o,l,a){if(!n)return;let u=Ci(s,t,e);if(u)return u[0]=Math.ceil((u[0]+(a?o/2:0))/o),u[1]=Math.ceil(u[1]/l),u[0]=Math.min(Math.max(u[0],1),i+(a?1:0)),u[1]=Math.min(Math.max(u[1],1),r),u}var Xt=class{constructor(t,e){this._renderService=t;this._charSizeService=e}getCoords(t,e,i,r,n){return Xo(window,t,e,i,r,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,n)}getMouseReportCoords(t,e){let i=Ci(window,t,e);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])}}};Xt=M([S(0,ce),S(1,nt)],Xt);var en=class{constructor(t,e){this._renderCallback=t;this._coreBrowserService=e;this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(t){return this._refreshCallbacks.push(t),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh())),this._animationFrame}refresh(t,e,i){this._rowCount=i,t=t!==void 0?t:0,e=e!==void 0?e:this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,t):t,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,e):e,!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 t=Math.max(this._rowStart,0),e=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(t,e),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let t of this._refreshCallbacks)t(0);this._refreshCallbacks=[]}};var tn={};Ll(tn,{getSafariVersion:()=>Ha,isChromeOS:()=>Ts,isFirefox:()=>Ss,isIpad:()=>Wa,isIphone:()=>Ua,isLegacyEdge:()=>Fa,isLinux:()=>Bi,isMac:()=>Zt,isNode:()=>Mi,isSafari:()=>Zo,isWindows:()=>Es});var Mi=typeof process<"u"&&"title"in process,Pi=Mi?"node":navigator.userAgent,Oi=Mi?"node":navigator.platform,Ss=Pi.includes("Firefox"),Fa=Pi.includes("Edge"),Zo=/^((?!chrome|android).)*safari/i.test(Pi);function Ha(){if(!Zo)return 0;let s=Pi.match(/Version\/(\d+)/);return s===null||s.length<2?0:parseInt(s[1])}var Zt=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(Oi),Wa=Oi==="iPad",Ua=Oi==="iPhone",Es=["Windows","Win16","Win32","WinCE"].includes(Oi),Bi=Oi.indexOf("Linux")>=0,Ts=/\bCrOS\b/.test(Pi);var rn=class{constructor(){this._tasks=[];this._i=0}enqueue(t){this._tasks.push(t),this._start()}flush(){for(;this._in){r-e<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-e))}ms`),this._start();return}r=n}this.clear()}},Is=class extends rn{_requestCallback(t){return setTimeout(()=>t(this._createDeadline(16)))}_cancelCallback(t){clearTimeout(t)}_createDeadline(t){let e=performance.now()+t;return{timeRemaining:()=>Math.max(0,e-performance.now())}}},ys=class extends rn{_requestCallback(t){return requestIdleCallback(t)}_cancelCallback(t){cancelIdleCallback(t)}},Jt=!Mi&&"requestIdleCallback"in window?ys:Is,nn=class{constructor(){this._queue=new Jt}set(t){this._queue.clear(),this._queue.enqueue(t)}flush(){this._queue.flush()}};var Qt=class extends D{constructor(e,i,r,n,o,l,a,u,h){super();this._rowCount=e;this._optionsService=r;this._charSizeService=n;this._coreService=o;this._coreBrowserService=u;this._renderer=this._register(new ye);this._pausedResizeTask=new nn;this._observerDisposable=this._register(new ye);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 v);this.onDimensionsChange=this._onDimensionsChange.event;this._onRenderedViewportChange=this._register(new v);this.onRenderedViewportChange=this._onRenderedViewportChange.event;this._onRender=this._register(new v);this.onRender=this._onRender.event;this._onRefreshRequest=this._register(new v);this.onRefreshRequest=this._onRefreshRequest.event;this._renderDebouncer=new en((c,d)=>this._renderRows(c,d),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new xs(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(C(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(a.onResize(()=>this._fullRefresh())),this._register(a.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(l.onDecorationRegistered(()=>this._fullRefresh())),this._register(l.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["customGlyphs","drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(a.cols,a.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(a.buffer.y,a.buffer.y,!0))),this._register(h.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,i),this._register(this._coreBrowserService.onWindowChange(c=>this._registerIntersectionObserver(c,i)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,i){if("IntersectionObserver"in e){let r=new e.IntersectionObserver(n=>this._handleIntersectionChange(n[n.length-1]),{threshold:0});r.observe(i),this._observerDisposable.value=C(()=>r.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,i,r=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,i);return}let n=this._syncOutputHandler.flush();n&&(e=Math.min(e,n.start),i=Math.max(i,n.end)),r||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,i,this._rowCount)}_renderRows(e,i){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,i);return}e=Math.min(e,this._rowCount-1),i=Math.min(i,this._rowCount-1),this._renderer.value.renderRows(e,i),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:i}),this._onRender.fire({start:e,end:i}),this._isNextRenderRedrawOnly=!0}}resize(e,i){this._rowCount=i,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(i=>this.refreshRows(i.start,i.end,!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,i){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,i)):this._renderer.value.handleResize(e,i),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,i,r){this._selectionState.start=e,this._selectionState.end=i,this._selectionState.columnSelectMode=r,this._renderer.value?.handleSelectionChanged(e,i,r)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};Qt=M([S(2,H),S(3,nt),S(4,ge),S(5,Be),S(6,F),S(7,ae),S(8,Re)],Qt);var xs=class{constructor(t,e,i){this._coreBrowserService=t;this._coreService=e;this._onTimeout=i;this._start=0;this._end=0;this._isBuffering=!1}bufferRows(t,e){this._isBuffering?(this._start=Math.min(this._start,t),this._end=Math.max(this._end,e)):(this._start=t,this._end=e,this._isBuffering=!0),this._timeout===void 0&&(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 t={start:this._start,end:this._end};return this._isBuffering=!1,t}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Jo(s,t,e,i){let r=e.buffer.x,n=e.buffer.y;if(!e.buffer.hasScrollback)return Ga(r,n,s,t,e,i)+sn(n,t,e,i)+$a(r,n,s,t,e,i);let o;if(n===t)return o=r>s?"D":"C",Fi(Math.abs(r-s),Ni(o,i));o=n>t?"D":"C";let l=Math.abs(n-t),a=za(n>t?s:r,e)+(l-1)*e.cols+1+Ka(n>t?r:s,e);return Fi(a,Ni(o,i))}function Ka(s,t){return s-1}function za(s,t){return t.cols-s}function Ga(s,t,e,i,r,n){return sn(t,i,r,n).length===0?"":Fi(el(s,t,s,t-gt(t,r),!1,r).length,Ni("D",n))}function sn(s,t,e,i){let r=s-gt(s,e),n=t-gt(t,e),o=Math.abs(r-n)-Va(s,t,e);return Fi(o,Ni(Qo(s,t),i))}function $a(s,t,e,i,r,n){let o;sn(t,i,r,n).length>0?o=i-gt(i,r):o=t;let l=i,a=qa(s,t,e,i,r,n);return Fi(el(s,o,e,l,a==="C",r).length,Ni(a,n))}function Va(s,t,e){let i=0,r=s-gt(s,e),n=t-gt(t,e);for(let o=0;o=0&&s0?o=i-gt(i,r):o=t,s=e&&ot?"A":"B"}function el(s,t,e,i,r,n){let o=s,l=t,a="";for(;(o!==e||l!==i)&&l>=0&&ln.cols-1?(a+=n.buffer.translateBufferLineToString(l,!1,s,o),o=0,s=0,l++):!r&&o<0&&(a+=n.buffer.translateBufferLineToString(l,!1,0,s+1),o=n.cols-1,s=o,l--);return a+n.buffer.translateBufferLineToString(l,!1,s,o)}function Ni(s,t){let e=t?"O":"[";return b.ESC+e+s}function Fi(s,t){s=Math.floor(s);let e="";for(let i=0;ithis._bufferService.cols?t%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(t/this._bufferService.cols)-1]:[t%this._bufferService.cols,this.selectionStart[1]+Math.floor(t/this._bufferService.cols)]:[t,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let t=this.selectionStart[0]+this.selectionStartLength;return t>this._bufferService.cols?[t%this._bufferService.cols,this.selectionStart[1]+Math.floor(t/this._bufferService.cols)]:[Math.max(t,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let t=this.selectionStart,e=this.selectionEnd;return!t||!e?!1:t[1]>e[1]||t[1]===e[1]&&t[0]>e[0]}handleTrim(t){return this.selectionStart&&(this.selectionStart[1]-=t),this.selectionEnd&&(this.selectionEnd[1]-=t),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function ws(s,t){if(s.start.y>s.end.y)throw new Error(`Buffer range end (${s.end.x}, ${s.end.y}) cannot be before start (${s.start.x}, ${s.start.y})`);return t*(s.end.y-s.start.y)+(s.end.x-s.start.x+1)}var Ds=50,Ya=15,ja=50,Xa=500,Za="\xA0",Ja=new RegExp(Za,"g");var ei=class extends D{constructor(e,i,r,n,o,l,a,u,h){super();this._element=e;this._screenElement=i;this._linkifier=r;this._bufferService=n;this._coreService=o;this._mouseService=l;this._optionsService=a;this._renderService=u;this._coreBrowserService=h;this._dragScrollAmount=0;this._enabled=!0;this._workCell=new q;this._mouseDownTimeStamp=0;this._oldHasSelection=!1;this._oldSelectionStart=void 0;this._oldSelectionEnd=void 0;this._onLinuxMouseSelection=this._register(new v);this.onLinuxMouseSelection=this._onLinuxMouseSelection.event;this._onRedrawRequest=this._register(new v);this.onRequestRedraw=this._onRedrawRequest.event;this._onSelectionChange=this._register(new v);this.onSelectionChange=this._onSelectionChange.event;this._onRequestScrollLines=this._register(new v);this.onRequestScrollLines=this._onRequestScrollLines.event;this._mouseMoveListener=c=>this._handleMouseMove(c),this._mouseUpListener=c=>this._handleMouseUp(c),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(c=>this._handleTrim(c)),this._register(this._bufferService.buffers.onBufferActivate(c=>this._handleBufferActivate(c))),this.enable(),this._model=new on(this._bufferService),this._activeSelectionMode=0,this._register(C(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(c=>{c.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,i=this._model.finalSelectionEnd;return!e||!i?!1:e[0]!==i[0]||e[1]!==i[1]}get selectionText(){let e=this._model.finalSelectionStart,i=this._model.finalSelectionEnd;if(!e||!i)return"";let r=this._bufferService.buffer,n=[];if(this._activeSelectionMode===3){if(e[0]===i[0])return"";let l=e[0]l.replace(Ja," ")).join(Es?`\r +`:` +`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),Bi&&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 i=this._getMouseBufferCoords(e),r=this._model.finalSelectionStart,n=this._model.finalSelectionEnd;return!r||!n||!i?!1:this._areCoordsInSelection(i,r,n)}isCellInSelection(e,i){let r=this._model.finalSelectionStart,n=this._model.finalSelectionEnd;return!r||!n?!1:this._areCoordsInSelection([e,i],r,n)}_areCoordsInSelection(e,i,r){return e[1]>i[1]&&e[1]=i[0]&&e[0]=i[0]}_selectWordAtCursor(e,i){let r=this._linkifier.currentLink?.link?.range;if(r)return this._model.selectionStart=[r.start.x-1,r.start.y-1],this._model.selectionStartLength=ws(r,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let n=this._getMouseBufferCoords(e);return n?(this._selectWordAt(n,i),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,i){this._model.clearSelection(),e=Math.max(e,0),i=Math.min(i,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,i],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let i=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(i)return i[0]--,i[1]--,i[1]+=this._bufferService.buffer.ydisp,i}_getMouseEventScrollAmount(e){let i=Ci(this._coreBrowserService.window,e,this._screenElement)[1],r=this._renderService.dimensions.css.canvas.height;return i>=0&&i<=r?0:(i>r&&(i-=r),i=Math.min(Math.max(i,-Ds),Ds),i/=Ds,i/Math.abs(i)+Math.round(i*(Ya-1)))}shouldForceSelection(e){return Zt?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(),ja)}_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){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;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 i=this._getMouseBufferCoords(e);i&&(this._activeSelectionMode=2,this._selectLineAt(i[1]))}shouldColumnSelect(e){return e.altKey&&!(Zt&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let i=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 r=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 i=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&ithis._handleTrim(i))}_convertViewportColToCharacterIndex(e,i){let r=i;for(let n=0;i>=n;n++){let o=e.loadCell(n,this._workCell).getChars().length;this._workCell.getWidth()===0?r--:o>1&&i!==n&&(r+=o-1)}return r}setSelection(e,i,r){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,i],this._model.selectionStartLength=r,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,i,r=!0,n=!0){if(e[0]>=this._bufferService.cols)return;let o=this._bufferService.buffer,l=o.lines.get(e[1]);if(!l)return;let a=o.translateBufferLineToString(e[1],!1),u=this._convertViewportColToCharacterIndex(l,e[0]),h=u,c=e[0]-u,d=0,_=0,p=0,m=0;if(a.charAt(u)===" "){for(;u>0&&a.charAt(u-1)===" ";)u--;for(;h1&&(m+=I-1,h+=I-1);R>0&&u>0&&!this._isCharWordSeparator(l.loadCell(R-1,this._workCell));){l.loadCell(R-1,this._workCell);let k=this._workCell.getChars().length;this._workCell.getWidth()===0?(d++,R--):k>1&&(p+=k-1,u-=k-1),u--,R--}for(;O1&&(m+=k-1,h+=k-1),h++,O++}}h++;let f=u+c-d+p,A=Math.min(this._bufferService.cols,h-u+d+_-p-m);if(!(!i&&a.slice(u,h).trim()==="")){if(r&&f===0&&l.getCodePoint(0)!==32){let R=o.lines.get(e[1]-1);if(R&&l.isWrapped&&R.getCodePoint(this._bufferService.cols-1)!==32){let O=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(O){let I=this._bufferService.cols-O.start;f-=I,A+=I}}}if(n&&f+A===this._bufferService.cols&&l.getCodePoint(this._bufferService.cols-1)!==32){let R=o.lines.get(e[1]+1);if(R?.isWrapped&&R.getCodePoint(0)!==32){let O=this._getWordAt([0,e[1]+1],!1,!1,!0);O&&(A+=O.length)}}return{start:f,length:A}}}_selectWordAt(e,i){let r=this._getWordAt(e,i);if(r){for(;r.start<0;)r.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[r.start,e[1]],this._model.selectionStartLength=r.length}}_selectToWordAt(e){let i=this._getWordAt(e,!0);if(i){let r=e[1];for(;i.start<0;)i.start+=this._bufferService.cols,r--;if(!this._model.areSelectionValuesReversed())for(;i.start+i.length>this._bufferService.cols;)i.length-=this._bufferService.cols,r++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?i.start:i.start+i.length,r]}}_isCharWordSeparator(e){return e.getWidth()===0?!1:this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let i=this._bufferService.buffer.getWrappedRangeForLine(e),r={start:{x:0,y:i.first},end:{x:this._bufferService.cols-1,y:i.last}};this._model.selectionStart=[0,i.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=ws(r,this._bufferService.cols)}};ei=M([S(3,F),S(4,ge),S(5,Dt),S(6,H),S(7,ce),S(8,ae)],ei);var Hi=class{constructor(){this._data={}}set(t,e,i){this._data[t]||(this._data[t]={}),this._data[t][e]=i}get(t,e){return this._data[t]?this._data[t][e]:void 0}clear(){this._data={}}};var Wi=class{constructor(){this._color=new Hi;this._css=new Hi}setCss(t,e,i){this._css.set(t,e,i)}getCss(t,e){return this._css.get(t,e)}setColor(t,e,i){this._color.set(t,e,i)}getColor(t,e){return this._color.get(t,e)}clear(){this._color.clear(),this._css.clear()}};var re=Object.freeze((()=>{let s=[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 e=0;e<216;e++){let i=t[e/36%6|0],r=t[e/6%6|0],n=t[e%6];s.push({css:j.toCss(i,r,n),rgba:j.toRgba(i,r,n)})}for(let e=0;e<24;e++){let i=8+e*10;s.push({css:j.toCss(i,i,i),rgba:j.toRgba(i,i,i)})}return s})());var St=z.toColor("#ffffff"),Ki=z.toColor("#000000"),tl=z.toColor("#ffffff"),il=Ki,Ui={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},Qa=St,ti=class extends D{constructor(e){super();this._optionsService=e;this._contrastCache=new Wi;this._halfContrastCache=new Wi;this._onChangeColors=this._register(new v);this.onChangeColors=this._onChangeColors.event;this._colors={foreground:St,background:Ki,cursor:tl,cursorAccent:il,selectionForeground:void 0,selectionBackgroundTransparent:Ui,selectionBackgroundOpaque:U.blend(Ki,Ui),selectionInactiveBackgroundTransparent:Ui,selectionInactiveBackgroundOpaque:U.blend(Ki,Ui),scrollbarSliderBackground:U.opacity(St,.2),scrollbarSliderHoverBackground:U.opacity(St,.4),scrollbarSliderActiveBackground:U.opacity(St,.5),overviewRulerBorder:St,ansi:re.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 i=this._colors;if(i.foreground=K(e.foreground,St),i.background=K(e.background,Ki),i.cursor=U.blend(i.background,K(e.cursor,tl)),i.cursorAccent=U.blend(i.background,K(e.cursorAccent,il)),i.selectionBackgroundTransparent=K(e.selectionBackground,Ui),i.selectionBackgroundOpaque=U.blend(i.background,i.selectionBackgroundTransparent),i.selectionInactiveBackgroundTransparent=K(e.selectionInactiveBackground,i.selectionBackgroundTransparent),i.selectionInactiveBackgroundOpaque=U.blend(i.background,i.selectionInactiveBackgroundTransparent),i.selectionForeground=e.selectionForeground?K(e.selectionForeground,ps):void 0,i.selectionForeground===ps&&(i.selectionForeground=void 0),U.isOpaque(i.selectionBackgroundTransparent)&&(i.selectionBackgroundTransparent=U.opacity(i.selectionBackgroundTransparent,.3)),U.isOpaque(i.selectionInactiveBackgroundTransparent)&&(i.selectionInactiveBackgroundTransparent=U.opacity(i.selectionInactiveBackgroundTransparent,.3)),i.scrollbarSliderBackground=K(e.scrollbarSliderBackground,U.opacity(i.foreground,.2)),i.scrollbarSliderHoverBackground=K(e.scrollbarSliderHoverBackground,U.opacity(i.foreground,.4)),i.scrollbarSliderActiveBackground=K(e.scrollbarSliderActiveBackground,U.opacity(i.foreground,.5)),i.overviewRulerBorder=K(e.overviewRulerBorder,Qa),i.ansi=re.slice(),i.ansi[0]=K(e.black,re[0]),i.ansi[1]=K(e.red,re[1]),i.ansi[2]=K(e.green,re[2]),i.ansi[3]=K(e.yellow,re[3]),i.ansi[4]=K(e.blue,re[4]),i.ansi[5]=K(e.magenta,re[5]),i.ansi[6]=K(e.cyan,re[6]),i.ansi[7]=K(e.white,re[7]),i.ansi[8]=K(e.brightBlack,re[8]),i.ansi[9]=K(e.brightRed,re[9]),i.ansi[10]=K(e.brightGreen,re[10]),i.ansi[11]=K(e.brightYellow,re[11]),i.ansi[12]=K(e.brightBlue,re[12]),i.ansi[13]=K(e.brightMagenta,re[13]),i.ansi[14]=K(e.brightCyan,re[14]),i.ansi[15]=K(e.brightWhite,re[15]),e.extendedAnsi){let r=Math.min(i.ansi.length-16,e.extendedAnsi.length);for(let n=0;no.index-l.index),r=[];for(let o of i){let l=this._services.get(o.id);if(!l)throw new Error(`[createInstance] ${t.name} depends on UNKNOWN service ${o.id._id}.`);r.push(l)}let n=i.length>0?i[0].index:e.length;if(e.length!==n)throw new Error(`[createInstance] First service dependency of ${t.name} at position ${n+1} conflicts with ${e.length} static arguments`);return new t(...e,...r)}};var ec={trace:0,debug:1,info:2,warn:3,error:4,off:5},tc="xterm.js: ",ii=class extends D{constructor(e){super();this._optionsService=e;this._logLevel=5;this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel())),ic=this}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=ec[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let i=0;ithis._length)for(let i=this._length;i=e;n--)this._array[this._getCyclicIndex(n+r.length)]=this._array[this._getCyclicIndex(n)];for(let n=0;nthis._maxLength){let n=this._length+r.length-this._maxLength;this._startIndex+=n,this._length=this._maxLength,this.onTrimEmitter.fire(n)}else this._length+=r.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,i,r){if(!(i<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+r<0)throw new Error("Cannot shift elements in list beyond index 0");if(r>0){for(let o=i-1;o>=0;o--)this.set(e+o+r,this.get(e+o));let n=e+i+r-this._length;if(n>0)for(this._length+=n;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let n=0;n>22,e&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):i]}set(t,e){this._data[t*B+1]=e[0],e[1].length>1?(this._combined[t]=e[1],this._data[t*B+0]=t|2097152|e[2]<<22):this._data[t*B+0]=e[1].charCodeAt(0)|e[2]<<22}getWidth(t){return this._data[t*B+0]>>22}hasWidth(t){return this._data[t*B+0]&12582912}getFg(t){return this._data[t*B+1]}getBg(t){return this._data[t*B+2]}hasContent(t){return this._data[t*B+0]&4194303}getCodePoint(t){let e=this._data[t*B+0];return e&2097152?this._combined[t].charCodeAt(this._combined[t].length-1):e&2097151}isCombined(t){return this._data[t*B+0]&2097152}getString(t){let e=this._data[t*B+0];return e&2097152?this._combined[t]:e&2097151?Ce(e&2097151):""}isProtected(t){return this._data[t*B+2]&536870912}loadCell(t,e){return an=t*B,e.content=this._data[an+0],e.fg=this._data[an+1],e.bg=this._data[an+2],e.content&2097152&&(e.combinedData=this._combined[t]),e.bg&268435456&&(e.extended=this._extendedAttrs[t]),e}setCell(t,e){e.content&2097152&&(this._combined[t]=e.combinedData),e.bg&268435456&&(this._extendedAttrs[t]=e.extended),this._data[t*B+0]=e.content,this._data[t*B+1]=e.fg,this._data[t*B+2]=e.bg}setCellFromCodepoint(t,e,i,r){r.bg&268435456&&(this._extendedAttrs[t]=r.extended),this._data[t*B+0]=e|i<<22,this._data[t*B+1]=r.fg,this._data[t*B+2]=r.bg}addCodepointToCell(t,e,i){let r=this._data[t*B+0];r&2097152?this._combined[t]+=Ce(e):r&2097151?(this._combined[t]=Ce(r&2097151)+Ce(e),r&=-2097152,r|=2097152):r=e|1<<22,i&&(r&=-12582913,r|=i<<22),this._data[t*B+0]=r}insertCells(t,e,i){if(t%=this.length,t&&this.getWidth(t-1)===2&&this.setCellFromCodepoint(t-1,0,1,i),e=0;--n)this.setCell(t+e+n,this.loadCell(t+n,r));for(let n=0;nthis.length){if(this._data.buffer.byteLength>=i*4)this._data=new Uint32Array(this._data.buffer,0,i);else{let r=new Uint32Array(i);r.set(this._data),this._data=r}for(let r=this.length;r=t&&delete this._combined[l]}let n=Object.keys(this._extendedAttrs);for(let o=0;o=t&&delete this._extendedAttrs[l]}}return this.length=t,i*4*Ls=0;--t)if(this._data[t*B+0]&4194303)return t+(this._data[t*B+0]>>22);return 0}getNoBgTrimmedLength(){for(let t=this.length-1;t>=0;--t)if(this._data[t*B+0]&4194303||this._data[t*B+2]&50331648)return t+(this._data[t*B+0]>>22);return 0}copyCellsFrom(t,e,i,r,n){let o=t._data;if(n)for(let a=r-1;a>=0;a--){for(let u=0;u=e&&(this._combined[u-e+i]=t._combined[u])}}translateToString(t,e,i,r){e=e??0,i=i??this.length,t&&(i=Math.min(i,this.getTrimmedLength())),r&&(r.length=0);let n="";for(;e>22||1}return r&&r.push(e),n}};function sl(s,t,e,i,r,n){let o=[];for(let l=0;l=l&&i0&&(f>c||h[f].getTrimmedLength()===0);f--)m++;m>0&&(o.push(l+h.length-m),o.push(m)),l+=h.length-1}return o}function ol(s,t){let e=[],i=0,r=t[i],n=0;for(let o=0;ori(s,u,t)).reduce((a,u)=>a+u),n=0,o=0,l=0;for(;la&&(n-=a,o++);let u=s[o].getWidth(n-1)===2;u&&n--;let h=u?e-1:e;i.push(h),l+=h}return i}function ri(s,t,e){if(t===s.length-1)return s[t].getTrimmedLength();let i=!s[t].hasContent(e-1)&&s[t].getWidth(e-1)===1,r=s[t+1].getWidth(0)===2;return i&&r?e-1:e}var un=class un{constructor(t){this.line=t;this.isDisposed=!1;this._disposables=[];this._id=un._nextId++;this._onDispose=this.register(new v);this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Ne(this._disposables),this._disposables.length=0)}register(t){return this._disposables.push(t),t}};un._nextId=1;var cn=un;var ne={},Je=ne.B;ne[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"};ne.A={"#":"\xA3"};ne.B=void 0;ne[4]={"#":"\xA3","@":"\xBE","[":"ij","\\":"\xBD","]":"|","{":"\xA8","|":"f","}":"\xBC","~":"\xB4"};ne.C=ne[5]={"[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};ne.R={"#":"\xA3","@":"\xE0","[":"\xB0","\\":"\xE7","]":"\xA7","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xA8"};ne.Q={"@":"\xE0","[":"\xE2","\\":"\xE7","]":"\xEA","^":"\xEE","`":"\xF4","{":"\xE9","|":"\xF9","}":"\xE8","~":"\xFB"};ne.K={"@":"\xA7","[":"\xC4","\\":"\xD6","]":"\xDC","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xDF"};ne.Y={"#":"\xA3","@":"\xA7","[":"\xB0","\\":"\xE7","]":"\xE9","`":"\xF9","{":"\xE0","|":"\xF2","}":"\xE8","~":"\xEC"};ne.E=ne[6]={"@":"\xC4","[":"\xC6","\\":"\xD8","]":"\xC5","^":"\xDC","`":"\xE4","{":"\xE6","|":"\xF8","}":"\xE5","~":"\xFC"};ne.Z={"#":"\xA3","@":"\xA7","[":"\xA1","\\":"\xD1","]":"\xBF","{":"\xB0","|":"\xF1","}":"\xE7"};ne.H=ne[7]={"@":"\xC9","[":"\xC4","\\":"\xD6","]":"\xC5","^":"\xDC","`":"\xE9","{":"\xE4","|":"\xF6","}":"\xE5","~":"\xFC"};ne["="]={"#":"\xF9","@":"\xE0","[":"\xE9","\\":"\xE7","]":"\xEA","^":"\xEE",_:"\xE8","`":"\xF4","{":"\xE4","|":"\xF6","}":"\xFC","~":"\xFB"};var cl=4294967295,$i=class{constructor(t,e,i){this._hasScrollback=t;this._optionsService=e;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=X.clone();this.savedCharset=Je;this.markers=[];this._nullCell=q.fromCharData([0,ir,1,0]);this._whitespaceCell=q.fromCharData([0,we,1,32]);this._isClearing=!1;this._memoryCleanupQueue=new Jt;this._memoryCleanupPosition=0;this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new zi(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(t){return t?(this._nullCell.fg=t.fg,this._nullCell.bg=t.bg,this._nullCell.extended=t.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new rt),this._nullCell}getWhitespaceCell(t){return t?(this._whitespaceCell.fg=t.fg,this._whitespaceCell.bg=t.bg,this._whitespaceCell.extended=t.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new rt),this._whitespaceCell}getBlankLine(t,e){return new Ze(this._bufferService.cols,this.getNullCell(t),e)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&ecl?cl:e}fillViewportRows(t){if(this.lines.length===0){t===void 0&&(t=X);let e=this._rows;for(;e--;)this.lines.push(this.getBlankLine(t))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new zi(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(t,e){let i=this.getNullCell(X),r=0,n=this._getCorrectBufferLength(e);if(n>this.lines.maxLength&&(this.lines.maxLength=n),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+o+1?(this.ybase--,o++,this.ydisp>0&&this.ydisp--):this.lines.push(new Ze(t,i)));else for(let l=this._rows;l>e;l--)this.lines.length>e+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(n0&&(this.lines.trimStart(l),this.ybase=Math.max(this.ybase-l,0),this.ydisp=Math.max(this.ydisp-l,0),this.savedY=Math.max(this.savedY-l,0)),this.lines.maxLength=n}this.x=Math.min(this.x,t-1),this.y=Math.min(this.y,e-1),o&&(this.y+=o),this.savedX=Math.min(this.savedX,t-1),this.scrollTop=0}if(this.scrollBottom=e-1,this._isReflowEnabled&&(this._reflow(t,e),this._cols>t))for(let o=0;o.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let t=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,t=!1);let e=0;for(;this._memoryCleanupPosition100)return!0;return t}get _isReflowEnabled(){let t=this._optionsService.rawOptions.windowsPty;return t&&t.buildNumber?this._hasScrollback&&t.backend==="conpty"&&t.buildNumber>=21376:this._hasScrollback&&!this._optionsService.rawOptions.windowsMode}_reflow(t,e){this._cols!==t&&(t>this._cols?this._reflowLarger(t,e):this._reflowSmaller(t,e))}_reflowLarger(t,e){let i=this._optionsService.rawOptions.reflowCursorLine,r=sl(this.lines,this._cols,t,this.ybase+this.y,this.getNullCell(X),i);if(r.length>0){let n=ol(this.lines,r);ll(this.lines,n.layout),this._reflowLargerAdjustViewport(t,e,n.countRemoved)}}_reflowLargerAdjustViewport(t,e,i){let r=this.getNullCell(X),n=i;for(;n-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;l--){let a=this.lines.get(l);if(!a||!a.isWrapped&&a.getTrimmedLength()<=t)continue;let u=[a];for(;a.isWrapped&&l>0;)a=this.lines.get(--l),u.unshift(a);if(!i){let I=this.ybase+this.y;if(I>=l&&I0&&(n.push({start:l+u.length+o,newLines:p}),o+=p.length),u.push(...p);let m=c.length-1,f=c[m];f===0&&(m--,f=c[m]);let A=u.length-d-1,R=h;for(;A>=0;){let I=Math.min(R,f);if(u[m]===void 0)break;if(u[m].copyCellsFrom(u[A],R-I,f-I,I,!0),f-=I,f===0&&(m--,f=c[m]),R-=I,R===0){A--;let k=Math.max(A,0);R=ri(u,k,this._cols)}}for(let I=0;I0;)this.ybase===0?this.y0){let l=[],a=[];for(let f=0;f=0;f--)if(d&&d.start>h+_){for(let A=d.newLines.length-1;A>=0;A--)this.lines.set(f--,d.newLines[A]);f++,l.push({index:h+1,amount:d.newLines.length}),_+=d.newLines.length,d=n[++c]}else this.lines.set(f,a[h--]);let p=0;for(let f=l.length-1;f>=0;f--)l[f].index+=p,this.lines.onInsertEmitter.fire(l[f]),p+=l[f].amount;let m=Math.max(0,u+o-this.lines.maxLength);m>0&&this.lines.onTrimEmitter.fire(m)}}translateBufferLineToString(t,e,i=0,r){let n=this.lines.get(t);return n?n.translateToString(e,i,r):""}getWrappedRangeForLine(t){let e=t,i=t;for(;e>0&&this.lines.get(e).isWrapped;)e--;for(;i+10;);return t>=this._cols?this._cols-1:t<0?0:t}nextStop(t){for(t==null&&(t=this.x);!this.tabs[++t]&&t=this._cols?this._cols-1:t<0?0:t}clearMarkers(t){this._isClearing=!0;for(let e=0;e{e.line-=i,e.line<0&&e.dispose()})),e.register(this.lines.onInsert(i=>{e.line>=i.index&&(e.line+=i.amount)})),e.register(this.lines.onDelete(i=>{e.line>=i.index&&e.linei.index&&(e.line-=i.amount)})),e.register(e.onDispose(()=>this._removeMarker(e))),e}_removeMarker(t){this._isClearing||this.markers.splice(this.markers.indexOf(t),1)}};var hn=class extends D{constructor(e,i){super();this._optionsService=e;this._bufferService=i;this._onBufferActivate=this._register(new v);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 $i(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new $i(!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,i){this._normal.resize(e,i),this._alt.resize(e,i),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}};var ks=2,Cs=1,ni=class extends D{constructor(e){super();this.isUserScrolling=!1;this._onResize=this._register(new v);this.onResize=this._onResize.event;this._onScroll=this._register(new v);this.onScroll=this._onScroll.event;this.cols=Math.max(e.rawOptions.cols||0,ks),this.rows=Math.max(e.rawOptions.rows||0,Cs),this.buffers=this._register(new hn(e,this)),this._register(this.buffers.onBufferActivate(i=>{this._onScroll.fire(i.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,i){let r=this.cols!==e,n=this.rows!==i;this.cols=e,this.rows=i,this.buffers.resize(e,i),this._onResize.fire({cols:e,rows:i,colsChanged:r,rowsChanged:n})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,i=!1){let r=this.buffer,n;n=this._cachedBlankLine,(!n||n.length!==this.cols||n.getFg(0)!==e.fg||n.getBg(0)!==e.bg)&&(n=r.getBlankLine(e,i),this._cachedBlankLine=n),n.isWrapped=i;let o=r.ybase+r.scrollTop,l=r.ybase+r.scrollBottom;if(r.scrollTop===0){let a=r.lines.isFull;l===r.lines.length-1?a?r.lines.recycle().copyFrom(n):r.lines.push(n.clone()):r.lines.splice(l+1,0,n.clone()),a?this.isUserScrolling&&(r.ydisp=Math.max(r.ydisp-1,0)):(r.ybase++,this.isUserScrolling||r.ydisp++)}else{let a=l-o+1;r.lines.shiftElements(o+1,a-1,-1),r.lines.set(l,n.clone())}this.isUserScrolling||(r.ydisp=r.ybase),this._onScroll.fire(r.ydisp)}scrollLines(e,i){let r=this.buffer;if(e<0){if(r.ydisp===0)return;this.isUserScrolling=!0}else e+r.ydisp>=r.ybase&&(this.isUserScrolling=!1);let n=r.ydisp;r.ydisp=Math.max(Math.min(r.ydisp+e,r.ybase),0),n!==r.ydisp&&(i||this._onScroll.fire(r.ydisp))}};ni=M([S(0,H)],ni);var si={cols:80,rows:24,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",customGlyphs:!0,drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollModifier:"alt",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:Zt,windowOptions:{},windowsMode:!1,windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",cancelEvents:!1,overviewRuler:{}},nc=["normal","bold","100","200","300","400","500","600","700","800","900"],dn=class extends D{constructor(e){super();this._onOptionChange=this._register(new v);this.onOptionChange=this._onOptionChange.event;let i={...si};for(let r in e)if(r in i)try{let n=e[r];i[r]=this._sanitizeAndValidateOption(r,n)}catch(n){console.error(n)}this.rawOptions=i,this.options={...i},this._setupOptions(),this._register(C(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,i){return this.onOptionChange(r=>{r===e&&i(this.rawOptions[e])})}onMultipleOptionChange(e,i){return this.onOptionChange(r=>{e.indexOf(r)!==-1&&i()})}_setupOptions(){let e=r=>{if(!(r in si))throw new Error(`No option with key "${r}"`);return this.rawOptions[r]},i=(r,n)=>{if(!(r in si))throw new Error(`No option with key "${r}"`);n=this._sanitizeAndValidateOption(r,n),this.rawOptions[r]!==n&&(this.rawOptions[r]=n,this._onOptionChange.fire(r))};for(let r in this.rawOptions){let n={get:e.bind(this,r),set:i.bind(this,r)};Object.defineProperty(this.options,r,n)}}_sanitizeAndValidateOption(e,i){switch(e){case"cursorStyle":if(i||(i=si[e]),!sc(i))throw new Error(`"${i}" is not a valid value for ${e}`);break;case"wordSeparator":i||(i=si[e]);break;case"fontWeight":case"fontWeightBold":if(typeof i=="number"&&1<=i&&i<=1e3)break;i=nc.includes(i)?i:si[e];break;case"cursorWidth":i=Math.floor(i);case"lineHeight":case"tabStopWidth":if(i<1)throw new Error(`${e} cannot be less than 1, value: ${i}`);break;case"minimumContrastRatio":i=Math.max(1,Math.min(21,Math.round(i*10)/10));break;case"scrollback":if(i=Math.min(i,4294967295),i<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(i<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${i}`);break;case"rows":case"cols":if(!i&&i!==0)throw new Error(`${e} must be numeric, value: ${i}`);break;case"windowsPty":i=i??{};break}return i}};function sc(s){return s==="block"||s==="underline"||s==="bar"}function oi(s,t=5){if(typeof s!="object")return s;let e=Array.isArray(s)?[]:{};for(let i in s)e[i]=t<=1?s[i]:s[i]&&oi(s[i],t-1);return e}var ul=Object.freeze({insertMode:!1}),hl=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,wraparound:!0}),li=class extends D{constructor(e,i,r){super();this._bufferService=e;this._logService=i;this._optionsService=r;this.isCursorInitialized=!1;this.isCursorHidden=!1;this._onData=this._register(new v);this.onData=this._onData.event;this._onUserInput=this._register(new v);this.onUserInput=this._onUserInput.event;this._onBinary=this._register(new v);this.onBinary=this._onBinary.event;this._onRequestScrollToBottom=this._register(new v);this.onRequestScrollToBottom=this._onRequestScrollToBottom.event;this.modes=oi(ul),this.decPrivateModes=oi(hl)}reset(){this.modes=oi(ul),this.decPrivateModes=oi(hl)}triggerDataEvent(e,i=!1){if(this._optionsService.rawOptions.disableStdin)return;let r=this._bufferService.buffer;i&&this._optionsService.rawOptions.scrollOnUserInput&&r.ybase!==r.ydisp&&this._onRequestScrollToBottom.fire(),i&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(n=>n.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(i=>i.charCodeAt(0))),this._onBinary.fire(e))}};li=M([S(0,F),S(1,nr),S(2,H)],li);var dl={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:s=>s.button===4||s.action!==1?!1:(s.ctrl=!1,s.alt=!1,s.shift=!1,!0)},VT200:{events:19,restrict:s=>s.action!==32},DRAG:{events:23,restrict:s=>!(s.action===32&&s.button===3)},ANY:{events:31,restrict:s=>!0}};function Ms(s,t){let e=(s.ctrl?16:0)|(s.shift?4:0)|(s.alt?8:0);return s.button===4?(e|=64,e|=s.action):(e|=s.button&3,s.button&4&&(e|=64),s.button&8&&(e|=128),s.action===32?e|=32:s.action===0&&!t&&(e|=3)),e}var Ps=String.fromCharCode,fl={DEFAULT:s=>{let t=[Ms(s,!1)+32,s.col+32,s.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`\x1B[M${Ps(t[0])}${Ps(t[1])}${Ps(t[2])}`},SGR:s=>{let t=s.action===0&&s.button!==4?"m":"M";return`\x1B[<${Ms(s,!0)};${s.col};${s.row}${t}`},SGR_PIXELS:s=>{let t=s.action===0&&s.button!==4?"m":"M";return`\x1B[<${Ms(s,!0)};${s.x};${s.y}${t}`}},ai=class extends D{constructor(e,i,r){super();this._bufferService=e;this._coreService=i;this._optionsService=r;this._protocols={};this._encodings={};this._activeProtocol="";this._activeEncoding="";this._lastEvent=null;this._wheelPartialScroll=0;this._onProtocolChange=this._register(new v);this.onProtocolChange=this._onProtocolChange.event;for(let n of Object.keys(dl))this.addProtocol(n,dl[n]);for(let n of Object.keys(fl))this.addEncoding(n,fl[n]);this.reset()}addProtocol(e,i){this._protocols[e]=i}addEncoding(e,i){this._encodings[e]=i}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,i,r){if(e.deltaY===0||e.shiftKey||i===void 0||r===void 0)return 0;let n=i/r,o=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(o/=n+0,Math.abs(e.deltaY)<50&&(o*=.3),this._wheelPartialScroll+=o,o=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(o*=this._bufferService.rows),o}_applyScrollModifier(e,i){return i.altKey||i.ctrlKey||i.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 i=this._encodings[this._activeEncoding](e);return i&&(this._activeEncoding==="DEFAULT"?this._coreService.triggerBinaryEvent(i):this._coreService.triggerDataEvent(i,!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,i,r){if(r){if(e.x!==i.x||e.y!==i.y)return!1}else if(e.col!==i.col||e.row!==i.row)return!1;return!(e.button!==i.button||e.action!==i.action||e.ctrl!==i.ctrl||e.alt!==i.alt||e.shift!==i.shift)}};ai=M([S(0,F),S(1,ge),S(2,H)],ai);var Os=[[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]],ac=[[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]],se;function cc(s,t){let e=0,i=t.length-1,r;if(st[i][1])return!1;for(;i>=e;)if(r=e+i>>1,s>t[r][1])e=r+1;else if(s=131072&&t<=196605||t>=196608&&t<=262141?2:1}charProperties(t,e){let i=this.wcwidth(t),r=i===0&&e!==0;if(r){let n=Ae.extractWidth(e);n===0?r=!1:n>i&&(i=n)}return Ae.createPropertyValue(0,i,r)}};var Ae=class s{constructor(){this._providers=Object.create(null);this._active="";this._onChange=new v;this.onChange=this._onChange.event;let t=new fn;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,e,i=!1){return(t&16777215)<<3|(e&3)<<1|(i?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 e=0,i=0,r=t.length;for(let n=0;n=r)return e+this.wcwidth(o);let u=t.charCodeAt(n);56320<=u&&u<=57343?o=(o-55296)*1024+u-56320+65536:e+=this.wcwidth(u)}let l=this.charProperties(o,i),a=s.extractWidth(l);s.extractShouldJoin(l)&&(a-=s.extractWidth(i)),e+=a,i=l}return e}charProperties(t,e){return this._activeProvider.charProperties(t,e)}};var pn=class{constructor(){this.glevel=0;this._charsets=[]}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(t){this.glevel=t,this.charset=this._charsets[t]}setgCharset(t,e){this._charsets[t]=e,this.glevel===t&&(this.charset=e)}};function Bs(s){let e=s.buffer.lines.get(s.buffer.ybase+s.buffer.y-1)?.get(s.cols-1),i=s.buffer.lines.get(s.buffer.ybase+s.buffer.y);i&&e&&(i.isWrapped=e[3]!==0&&e[3]!==32)}var Vi=2147483647,uc=256,ci=class s{constructor(t=32,e=32){this.maxLength=t;this.maxSubParamsLength=e;if(e>uc)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(t),this.length=0,this._subParams=new Int32Array(e),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(t),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let e=new s;if(!t.length)return e;for(let i=Array.isArray(t[0])?1:0;i>8,r=this._subParamsIdx[e]&255;r-i>0&&t.push(Array.prototype.slice.call(this._subParams,i,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>Vi?Vi: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>Vi?Vi:t,this._subParamsIdx[this.length-1]++}}hasSubParams(t){return(this._subParamsIdx[t]&255)-(this._subParamsIdx[t]>>8)>0}getSubParams(t){let e=this._subParamsIdx[t]>>8,i=this._subParamsIdx[t]&255;return i-e>0?this._subParams.subarray(e,i):null}getSubParamsAll(){let t={};for(let e=0;e>8,r=this._subParamsIdx[e]&255;r-i>0&&(t[e]=this._subParams.slice(i,r))}return t}addDigit(t){let e;if(this._rejectDigits||!(e=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let i=this._digitIsSub?this._subParams:this.params,r=i[e-1];i[e-1]=~r?Math.min(r*10+t,Vi):t}};var qi=[],mn=class{constructor(){this._state=0;this._active=qi;this._id=-1;this._handlers=Object.create(null);this._handlerFb=()=>{};this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(t,e){this._handlers[t]===void 0&&(this._handlers[t]=[]);let i=this._handlers[t];return i.push(e),{dispose:()=>{let r=i.indexOf(e);r!==-1&&i.splice(r,1)}}}clearHandler(t){this._handlers[t]&&delete this._handlers[t]}setHandlerFallback(t){this._handlerFb=t}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=qi}reset(){if(this._state===2)for(let t=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;t>=0;--t)this._active[t].end(!1);this._stack.paused=!1,this._active=qi,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||qi,!this._active.length)this._handlerFb(this._id,"START");else for(let t=this._active.length-1;t>=0;t--)this._active[t].start()}_put(t,e,i){if(!this._active.length)this._handlerFb(this._id,"PUT",It(t,e,i));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(t,e,i)}start(){this.reset(),this._state=1}put(t,e,i){if(this._state!==3){if(this._state===1)for(;e0&&this._put(t,e,i)}}end(t,e=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,"END",t);else{let i=!1,r=this._active.length-1,n=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,i=e,n=this._stack.fallThrough,this._stack.paused=!1),!n&&i===!1){for(;r>=0&&(i=this._active[r].end(t),i!==!0);r--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,i;r--}for(;r>=0;r--)if(i=this._active[r].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,i}this._active=qi,this._id=-1,this._state=0}}},pe=class{constructor(t){this._handler=t;this._data="";this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(t,e,i){this._hitLimit||(this._data+=It(t,e,i),this._data.length>1e7&&(this._data="",this._hitLimit=!0))}end(t){let e=!1;if(this._hitLimit)e=!1;else if(t&&(e=this._handler(this._data),e instanceof Promise))return e.then(i=>(this._data="",this._hitLimit=!1,i));return this._data="",this._hitLimit=!1,e}};var Yi=[],_n=class{constructor(){this._handlers=Object.create(null);this._active=Yi;this._ident=0;this._handlerFb=()=>{};this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=Yi}registerHandler(t,e){this._handlers[t]===void 0&&(this._handlers[t]=[]);let i=this._handlers[t];return i.push(e),{dispose:()=>{let r=i.indexOf(e);r!==-1&&i.splice(r,1)}}}clearHandler(t){this._handlers[t]&&delete this._handlers[t]}setHandlerFallback(t){this._handlerFb=t}reset(){if(this._active.length)for(let t=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;t>=0;--t)this._active[t].unhook(!1);this._stack.paused=!1,this._active=Yi,this._ident=0}hook(t,e){if(this.reset(),this._ident=t,this._active=this._handlers[t]||Yi,!this._active.length)this._handlerFb(this._ident,"HOOK",e);else for(let i=this._active.length-1;i>=0;i--)this._active[i].hook(e)}put(t,e,i){if(!this._active.length)this._handlerFb(this._ident,"PUT",It(t,e,i));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(t,e,i)}unhook(t,e=!0){if(!this._active.length)this._handlerFb(this._ident,"UNHOOK",t);else{let i=!1,r=this._active.length-1,n=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,i=e,n=this._stack.fallThrough,this._stack.paused=!1),!n&&i===!1){for(;r>=0&&(i=this._active[r].unhook(t),i!==!0);r--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,i;r--}for(;r>=0;r--)if(i=this._active[r].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,i}this._active=Yi,this._ident=0}},ji=new ci;ji.addParam(0);var Xi=class{constructor(t){this._handler=t;this._data="";this._params=ji;this._hitLimit=!1}hook(t){this._params=t.length>1||t.params[0]?t.clone():ji,this._data="",this._hitLimit=!1}put(t,e,i){this._hitLimit||(this._data+=It(t,e,i),this._data.length>1e7&&(this._data="",this._hitLimit=!0))}unhook(t){let e=!1;if(this._hitLimit)e=!1;else if(t&&(e=this._handler(this._data,this._params),e instanceof Promise))return e.then(i=>(this._params=ji,this._data="",this._hitLimit=!1,i));return this._params=ji,this._data="",this._hitLimit=!1,e}};var Fs=class{constructor(t){this.table=new Uint8Array(t)}setDefault(t,e){this.table.fill(t<<4|e)}add(t,e,i,r){this.table[e<<8|t]=i<<4|r}addMany(t,e,i,r){for(let n=0;nu),i=(a,u)=>e.slice(a,u),r=i(32,127),n=i(0,24);n.push(25),n.push.apply(n,i(28,32));let o=i(0,14),l;s.setDefault(1,0),s.addMany(r,0,2,0);for(l in o)s.addMany([24,26,153,154],l,3,0),s.addMany(i(128,144),l,3,0),s.addMany(i(144,152),l,3,0),s.add(156,l,0,0),s.add(27,l,11,1),s.add(157,l,4,8),s.addMany([152,158,159],l,0,7),s.add(155,l,11,3),s.add(144,l,11,9);return s.addMany(n,0,3,0),s.addMany(n,1,3,1),s.add(127,1,0,1),s.addMany(n,8,0,8),s.addMany(n,3,3,3),s.add(127,3,0,3),s.addMany(n,4,3,4),s.add(127,4,0,4),s.addMany(n,6,3,6),s.addMany(n,5,3,5),s.add(127,5,0,5),s.addMany(n,2,3,2),s.add(127,2,0,2),s.add(93,1,4,8),s.addMany(r,8,5,8),s.add(127,8,5,8),s.addMany([156,27,24,26,7],8,6,0),s.addMany(i(28,32),8,0,8),s.addMany([88,94,95],1,0,7),s.addMany(r,7,0,7),s.addMany(n,7,0,7),s.add(156,7,0,0),s.add(127,7,0,7),s.add(91,1,11,3),s.addMany(i(64,127),3,7,0),s.addMany(i(48,60),3,8,4),s.addMany([60,61,62,63],3,9,4),s.addMany(i(48,60),4,8,4),s.addMany(i(64,127),4,7,0),s.addMany([60,61,62,63],4,0,6),s.addMany(i(32,64),6,0,6),s.add(127,6,0,6),s.addMany(i(64,127),6,0,0),s.addMany(i(32,48),3,9,5),s.addMany(i(32,48),5,9,5),s.addMany(i(48,64),5,0,6),s.addMany(i(64,127),5,7,0),s.addMany(i(32,48),4,9,5),s.addMany(i(32,48),1,9,2),s.addMany(i(32,48),2,9,2),s.addMany(i(48,127),2,10,0),s.addMany(i(48,80),1,10,0),s.addMany(i(81,88),1,10,0),s.addMany([89,90,92],1,10,0),s.addMany(i(96,127),1,10,0),s.add(80,1,11,9),s.addMany(n,9,0,9),s.add(127,9,0,9),s.addMany(i(28,32),9,0,9),s.addMany(i(32,48),9,9,12),s.addMany(i(48,60),9,8,10),s.addMany([60,61,62,63],9,9,10),s.addMany(n,11,0,11),s.addMany(i(32,128),11,0,11),s.addMany(i(28,32),11,0,11),s.addMany(n,10,0,10),s.add(127,10,0,10),s.addMany(i(28,32),10,0,10),s.addMany(i(48,60),10,8,10),s.addMany([60,61,62,63],10,0,11),s.addMany(i(32,48),10,9,12),s.addMany(n,12,0,12),s.add(127,12,0,12),s.addMany(i(28,32),12,0,12),s.addMany(i(32,48),12,9,12),s.addMany(i(48,64),12,0,11),s.addMany(i(64,127),12,12,13),s.addMany(i(64,127),10,12,13),s.addMany(i(64,127),9,12,13),s.addMany(n,13,13,13),s.addMany(r,13,13,13),s.add(127,13,0,13),s.addMany([27,156,24,26],13,14,0),s.add(ke,0,2,0),s.add(ke,8,5,8),s.add(ke,6,0,6),s.add(ke,11,0,11),s.add(ke,13,13,13),s}(),bn=class extends D{constructor(e=hc){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 ci,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(i,r,n)=>{},this._executeHandlerFb=i=>{},this._csiHandlerFb=(i,r)=>{},this._escHandlerFb=i=>{},this._errorHandlerFb=i=>i,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(C(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new mn),this._dcsParser=this._register(new _n),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,i=[64,126]){let r=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(r=e.prefix.charCodeAt(0),r&&60>r||r>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 o=0;ol||l>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");r<<=8,r|=l}}if(e.final.length!==1)throw new Error("final must be a single byte");let n=e.final.charCodeAt(0);if(i[0]>n||n>i[1])throw new Error(`final must be in range ${i[0]} .. ${i[1]}`);return r<<=8,r|=n,r}identToString(e){let i=[];for(;e;)i.push(String.fromCharCode(e&255)),e>>=8;return i.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,i){let r=this._identifier(e,[48,126]);this._escHandlers[r]===void 0&&(this._escHandlers[r]=[]);let n=this._escHandlers[r];return n.push(i),{dispose:()=>{let o=n.indexOf(i);o!==-1&&n.splice(o,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,i){this._executeHandlers[e.charCodeAt(0)]=i}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,i){let r=this._identifier(e);this._csiHandlers[r]===void 0&&(this._csiHandlers[r]=[]);let n=this._csiHandlers[r];return n.push(i),{dispose:()=>{let o=n.indexOf(i);o!==-1&&n.splice(o,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,i){return this._dcsParser.registerHandler(this._identifier(e),i)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,i){return this._oscParser.registerHandler(e,i)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.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,i,r,n,o){this._parseStack.state=e,this._parseStack.handlers=i,this._parseStack.handlerPos=r,this._parseStack.transition=n,this._parseStack.chunkPos=o}parse(e,i,r){let n=0,o=0,l=0,a;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,l=this._parseStack.chunkPos+1;else{if(r===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");let u=this._parseStack.handlers,h=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(r===!1&&h>-1){for(;h>=0&&(a=u[h](this._params),a!==!0);h--)if(a instanceof Promise)return this._parseStack.handlerPos=h,a}this._parseStack.handlers=[];break;case 4:if(r===!1&&h>-1){for(;h>=0&&(a=u[h](),a!==!0);h--)if(a instanceof Promise)return this._parseStack.handlerPos=h,a}this._parseStack.handlers=[];break;case 6:if(n=e[this._parseStack.chunkPos],a=this._dcsParser.unhook(n!==24&&n!==26,r),a)return a;n===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(n=e[this._parseStack.chunkPos],a=this._oscParser.end(n!==24&&n!==26,r),a)return a;n===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,l=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&15}for(let u=l;u>4){case 2:for(let m=u+1;;++m){if(m>=i||(n=e[m])<32||n>126&&n=i||(n=e[m])<32||n>126&&n=i||(n=e[m])<32||n>126&&n=i||(n=e[m])<32||n>126&&n=0&&(a=c[d](this._params),a!==!0);d--)if(a instanceof Promise)return this._preserveStack(3,c,d,o,u),a;d<0&&this._csiHandlerFb(this._collect<<8|n,this._params),this.precedingJoinState=0;break;case 8:do switch(n){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(n-48)}while(++u47&&n<60);u--;break;case 9:this._collect<<=8,this._collect|=n;break;case 10:let _=this._escHandlers[this._collect<<8|n],p=_?_.length-1:-1;for(;p>=0&&(a=_[p](),a!==!0);p--)if(a instanceof Promise)return this._preserveStack(4,_,p,o,u),a;p<0&&this._escHandlerFb(this._collect<<8|n),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|n,this._params);break;case 13:for(let m=u+1;;++m)if(m>=i||(n=e[m])===24||n===26||n===27||n>127&&n=i||(n=e[m])<32||n>127&&n>4:n>>8}return i}}function Hs(s,t){let e=s.toString(16),i=e.length<2?"0"+e:e;switch(t){case 4:return e[0];case 8:return i;case 12:return(i+i).slice(0,3);default:return i+i}}function ml(s,t=16){let[e,i,r]=s;return`rgb:${Hs(e,t)}/${Hs(i,t)}/${Hs(r,t)}`}var mc={"(":0,")":1,"*":2,"+":3,"-":1,".":2},ut=131072,_l=10;function bl(s,t){if(s>24)return t.setWinLines||!1;switch(s){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 vl=5e3,gl=0,vn=class extends D{constructor(e,i,r,n,o,l,a,u,h=new bn){super();this._bufferService=e;this._charsetService=i;this._coreService=r;this._logService=n;this._optionsService=o;this._oscLinkService=l;this._coreMouseService=a;this._unicodeService=u;this._parser=h;this._parseBuffer=new Uint32Array(4096);this._stringDecoder=new er;this._utf8Decoder=new tr;this._windowTitle="";this._iconName="";this._windowTitleStack=[];this._iconNameStack=[];this._curAttrData=X.clone();this._eraseAttrDataInternal=X.clone();this._onRequestBell=this._register(new v);this.onRequestBell=this._onRequestBell.event;this._onRequestRefreshRows=this._register(new v);this.onRequestRefreshRows=this._onRequestRefreshRows.event;this._onRequestReset=this._register(new v);this.onRequestReset=this._onRequestReset.event;this._onRequestSendFocus=this._register(new v);this.onRequestSendFocus=this._onRequestSendFocus.event;this._onRequestSyncScrollBar=this._register(new v);this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event;this._onRequestWindowsOptionsReport=this._register(new v);this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event;this._onA11yChar=this._register(new v);this.onA11yChar=this._onA11yChar.event;this._onA11yTab=this._register(new v);this.onA11yTab=this._onA11yTab.event;this._onCursorMove=this._register(new v);this.onCursorMove=this._onCursorMove.event;this._onLineFeed=this._register(new v);this.onLineFeed=this._onLineFeed.event;this._onScroll=this._register(new v);this.onScroll=this._onScroll.event;this._onTitleChange=this._register(new v);this.onTitleChange=this._onTitleChange.event;this._onColor=this._register(new v);this.onColor=this._onColor.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 Zi(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(c=>this._activeBuffer=c.activeBuffer)),this._parser.setCsiHandlerFallback((c,d)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(c),params:d.toArray()})}),this._parser.setEscHandlerFallback(c=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(c)})}),this._parser.setExecuteHandlerFallback(c=>{this._logService.debug("Unknown EXECUTE code: ",{code:c})}),this._parser.setOscHandlerFallback((c,d,_)=>{this._logService.debug("Unknown OSC code: ",{identifier:c,action:d,data:_})}),this._parser.setDcsHandlerFallback((c,d,_)=>{d==="HOOK"&&(_=_.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(c),action:d,payload:_})}),this._parser.setPrintHandler((c,d,_)=>this.print(c,d,_)),this._parser.registerCsiHandler({final:"@"},c=>this.insertChars(c)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},c=>this.scrollLeft(c)),this._parser.registerCsiHandler({final:"A"},c=>this.cursorUp(c)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},c=>this.scrollRight(c)),this._parser.registerCsiHandler({final:"B"},c=>this.cursorDown(c)),this._parser.registerCsiHandler({final:"C"},c=>this.cursorForward(c)),this._parser.registerCsiHandler({final:"D"},c=>this.cursorBackward(c)),this._parser.registerCsiHandler({final:"E"},c=>this.cursorNextLine(c)),this._parser.registerCsiHandler({final:"F"},c=>this.cursorPrecedingLine(c)),this._parser.registerCsiHandler({final:"G"},c=>this.cursorCharAbsolute(c)),this._parser.registerCsiHandler({final:"H"},c=>this.cursorPosition(c)),this._parser.registerCsiHandler({final:"I"},c=>this.cursorForwardTab(c)),this._parser.registerCsiHandler({final:"J"},c=>this.eraseInDisplay(c,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},c=>this.eraseInDisplay(c,!0)),this._parser.registerCsiHandler({final:"K"},c=>this.eraseInLine(c,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},c=>this.eraseInLine(c,!0)),this._parser.registerCsiHandler({final:"L"},c=>this.insertLines(c)),this._parser.registerCsiHandler({final:"M"},c=>this.deleteLines(c)),this._parser.registerCsiHandler({final:"P"},c=>this.deleteChars(c)),this._parser.registerCsiHandler({final:"S"},c=>this.scrollUp(c)),this._parser.registerCsiHandler({final:"T"},c=>this.scrollDown(c)),this._parser.registerCsiHandler({final:"X"},c=>this.eraseChars(c)),this._parser.registerCsiHandler({final:"Z"},c=>this.cursorBackwardTab(c)),this._parser.registerCsiHandler({final:"`"},c=>this.charPosAbsolute(c)),this._parser.registerCsiHandler({final:"a"},c=>this.hPositionRelative(c)),this._parser.registerCsiHandler({final:"b"},c=>this.repeatPrecedingCharacter(c)),this._parser.registerCsiHandler({final:"c"},c=>this.sendDeviceAttributesPrimary(c)),this._parser.registerCsiHandler({prefix:">",final:"c"},c=>this.sendDeviceAttributesSecondary(c)),this._parser.registerCsiHandler({final:"d"},c=>this.linePosAbsolute(c)),this._parser.registerCsiHandler({final:"e"},c=>this.vPositionRelative(c)),this._parser.registerCsiHandler({final:"f"},c=>this.hVPosition(c)),this._parser.registerCsiHandler({final:"g"},c=>this.tabClear(c)),this._parser.registerCsiHandler({final:"h"},c=>this.setMode(c)),this._parser.registerCsiHandler({prefix:"?",final:"h"},c=>this.setModePrivate(c)),this._parser.registerCsiHandler({final:"l"},c=>this.resetMode(c)),this._parser.registerCsiHandler({prefix:"?",final:"l"},c=>this.resetModePrivate(c)),this._parser.registerCsiHandler({final:"m"},c=>this.charAttributes(c)),this._parser.registerCsiHandler({final:"n"},c=>this.deviceStatus(c)),this._parser.registerCsiHandler({prefix:"?",final:"n"},c=>this.deviceStatusPrivate(c)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},c=>this.softReset(c)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},c=>this.setCursorStyle(c)),this._parser.registerCsiHandler({final:"r"},c=>this.setScrollRegion(c)),this._parser.registerCsiHandler({final:"s"},c=>this.saveCursor(c)),this._parser.registerCsiHandler({final:"t"},c=>this.windowOptions(c)),this._parser.registerCsiHandler({final:"u"},c=>this.restoreCursor(c)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},c=>this.insertColumns(c)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},c=>this.deleteColumns(c)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},c=>this.selectProtected(c)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},c=>this.requestMode(c,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},c=>this.requestMode(c,!1)),this._parser.setExecuteHandler(b.BEL,()=>this.bell()),this._parser.setExecuteHandler(b.LF,()=>this.lineFeed()),this._parser.setExecuteHandler(b.VT,()=>this.lineFeed()),this._parser.setExecuteHandler(b.FF,()=>this.lineFeed()),this._parser.setExecuteHandler(b.CR,()=>this.carriageReturn()),this._parser.setExecuteHandler(b.BS,()=>this.backspace()),this._parser.setExecuteHandler(b.HT,()=>this.tab()),this._parser.setExecuteHandler(b.SO,()=>this.shiftOut()),this._parser.setExecuteHandler(b.SI,()=>this.shiftIn()),this._parser.setExecuteHandler(Ai.IND,()=>this.index()),this._parser.setExecuteHandler(Ai.NEL,()=>this.nextLine()),this._parser.setExecuteHandler(Ai.HTS,()=>this.tabSet()),this._parser.registerOscHandler(0,new pe(c=>(this.setTitle(c),this.setIconName(c),!0))),this._parser.registerOscHandler(1,new pe(c=>this.setIconName(c))),this._parser.registerOscHandler(2,new pe(c=>this.setTitle(c))),this._parser.registerOscHandler(4,new pe(c=>this.setOrReportIndexedColor(c))),this._parser.registerOscHandler(8,new pe(c=>this.setHyperlink(c))),this._parser.registerOscHandler(10,new pe(c=>this.setOrReportFgColor(c))),this._parser.registerOscHandler(11,new pe(c=>this.setOrReportBgColor(c))),this._parser.registerOscHandler(12,new pe(c=>this.setOrReportCursorColor(c))),this._parser.registerOscHandler(104,new pe(c=>this.restoreIndexedColor(c))),this._parser.registerOscHandler(110,new pe(c=>this.restoreFgColor(c))),this._parser.registerOscHandler(111,new pe(c=>this.restoreBgColor(c))),this._parser.registerOscHandler(112,new pe(c=>this.restoreCursorColor(c))),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 c in ne)this._parser.registerEscHandler({intermediates:"(",final:c},()=>this.selectCharset("("+c)),this._parser.registerEscHandler({intermediates:")",final:c},()=>this.selectCharset(")"+c)),this._parser.registerEscHandler({intermediates:"*",final:c},()=>this.selectCharset("*"+c)),this._parser.registerEscHandler({intermediates:"+",final:c},()=>this.selectCharset("+"+c)),this._parser.registerEscHandler({intermediates:"-",final:c},()=>this.selectCharset("-"+c)),this._parser.registerEscHandler({intermediates:".",final:c},()=>this.selectCharset("."+c)),this._parser.registerEscHandler({intermediates:"/",final:c},()=>this.selectCharset("/"+c));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(c=>(this._logService.error("Parsing error: ",c),c)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new Xi((c,d)=>this.requestStatusString(c,d)))}getAttrData(){return this._curAttrData}_preserveStack(e,i,r,n){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=i,this._parseStack.decodedLength=r,this._parseStack.position=n}_logSlowResolvingAsync(e){this._logService.logLevel<=3&&Promise.race([e,new Promise((i,r)=>setTimeout(()=>r("#SLOW_TIMEOUT"),vl))]).catch(i=>{if(i!=="#SLOW_TIMEOUT")throw i;console.warn(`async parser handler taking longer than ${vl} ms`)})}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,i){let r,n=this._activeBuffer.x,o=this._activeBuffer.y,l=0,a=this._parseStack.paused;if(a){if(r=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,i))return this._logSlowResolvingAsync(r),r;n=this._parseStack.cursorStartX,o=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>ut&&(l=this._parseStack.position+ut)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e=="string"?` "${e}"`:` "${Array.prototype.map.call(e,c=>String.fromCharCode(c)).join("")}"`}`),this._logService.logLevel===0&&this._logService.trace("parsing data (codes)",typeof e=="string"?e.split("").map(c=>c.charCodeAt(0)):e),this._parseBuffer.lengthut)for(let c=l;c0&&_.getWidth(this._activeBuffer.x-1)===2&&_.setCellFromCodepoint(this._activeBuffer.x-1,0,1,d);let p=this._parser.precedingJoinState;for(let m=i;mu){if(h){let O=_,I=this._activeBuffer.x-R;for(this._activeBuffer.x=R,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),_=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),R>0&&_ instanceof Ze&&_.copyCellsFrom(O,I,0,R,!1);I=0;)_.setCellFromCodepoint(this._activeBuffer.x++,0,0,d);continue}if(c&&(_.insertCells(this._activeBuffer.x,o-R,this._activeBuffer.getNullCell(d)),_.getWidth(u-1)===2&&_.setCellFromCodepoint(u-1,0,1,d)),_.setCellFromCodepoint(this._activeBuffer.x++,n,o,d),o>0)for(;--o;)_.setCellFromCodepoint(this._activeBuffer.x++,0,0,d)}this._parser.precedingJoinState=p,this._activeBuffer.x0&&_.getWidth(this._activeBuffer.x)===0&&!_.hasContent(this._activeBuffer.x)&&_.setCellFromCodepoint(this._activeBuffer.x,0,1,d),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,i){return e.final==="t"&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,r=>bl(r.params[0],this._optionsService.rawOptions.windowOptions)?i(r):!0):this._parser.registerCsiHandler(e,i)}registerDcsHandler(e,i){return this._parser.registerDcsHandler(e,new Xi(i))}registerEscHandler(e,i){return this._parser.registerEscHandler(e,i)}registerOscHandler(e,i){return this._parser.registerOscHandler(e,new pe(i))}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,i){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+i):(this._activeBuffer.x=e,this._activeBuffer.y=i),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,i){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+i)}cursorUp(e){let i=this._activeBuffer.y-this._activeBuffer.scrollTop;return i>=0?this._moveCursor(0,-Math.min(i,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let i=this._activeBuffer.scrollBottom-this._activeBuffer.y;return i>=0?this._moveCursor(0,Math.min(i,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 i=e.params[0];return i===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:i===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let i=e.params[0]||1;for(;i--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let i=e.params[0]||1;for(;i--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let i=e.params[0];return i===1&&(this._curAttrData.bg|=536870912),(i===2||i===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,i,r,n=!1,o=!1){let l=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);l.replaceCells(i,r,this._activeBuffer.getNullCell(this._eraseAttrData()),o),n&&(l.isWrapped=!1)}_resetBufferLine(e,i=!1){let r=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);r&&(r.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),i),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),r.isWrapped=!1)}eraseInDisplay(e,i=!1){this._restrictCursor(this._bufferService.cols);let r;switch(e.params[0]){case 0:for(r=this._activeBuffer.y,this._dirtyRowTracker.markDirty(r),this._eraseInBufferLine(r++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,i);r=this._bufferService.cols&&(this._activeBuffer.lines.get(r+1).isWrapped=!1);r--;)this._resetBufferLine(r,i);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(r=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,r-1);r--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+r)?.getTrimmedLength(););for(;r>=0;r--)this._bufferService.scroll(this._eraseAttrData())}else{for(r=this._bufferService.rows,this._dirtyRowTracker.markDirty(r-1);r--;)this._resetBufferLine(r,i);this._dirtyRowTracker.markDirty(0)}break;case 3:let n=this._activeBuffer.lines.length-this._bufferService.rows;n>0&&(this._activeBuffer.lines.trimStart(n),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-n,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-n,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,i=!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,i);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,i);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,i);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let i=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 c=h;for(let d=1;d0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent(b.ESC+"[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent(b.ESC+"[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent(b.ESC+"[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent(b.ESC+"[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent(b.ESC+"[>83;40003;0c")),!0}_is(e){return(this._optionsService.rawOptions.termName+"").indexOf(e)===0}setMode(e){for(let i=0;i(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"))(r||={});let n=this._coreService.decPrivateModes,{activeProtocol:o,activeEncoding:l}=this._coreMouseService,a=this._coreService,{buffers:u,cols:h}=this._bufferService,{active:c,alt:d}=u,_=this._optionsService.rawOptions,p=(A,R)=>(a.triggerDataEvent(`${b.ESC}[${i?"":"?"}${A};${R}$y`),!0),m=A=>A?1:2,f=e.params[0];return i?f===2?p(f,4):f===4?p(f,m(a.modes.insertMode)):f===12?p(f,3):f===20?p(f,m(_.convertEol)):p(f,0):f===1?p(f,m(n.applicationCursorKeys)):f===3?p(f,_.windowOptions.setWinLines?h===80?2:h===132?1:0:0):f===6?p(f,m(n.origin)):f===7?p(f,m(n.wraparound)):f===8?p(f,3):f===9?p(f,m(o==="X10")):f===12?p(f,m(_.cursorBlink)):f===25?p(f,m(!a.isCursorHidden)):f===45?p(f,m(n.reverseWraparound)):f===66?p(f,m(n.applicationKeypad)):f===67?p(f,4):f===1e3?p(f,m(o==="VT200")):f===1002?p(f,m(o==="DRAG")):f===1003?p(f,m(o==="ANY")):f===1004?p(f,m(n.sendFocus)):f===1005?p(f,4):f===1006?p(f,m(l==="SGR")):f===1015?p(f,4):f===1016?p(f,m(l==="SGR_PIXELS")):f===1048?p(f,1):f===47||f===1047||f===1049?p(f,m(c===d)):f===2004?p(f,m(n.bracketedPasteMode)):f===2026?p(f,m(n.synchronizedOutput)):p(f,0)}_updateAttrColor(e,i,r,n,o){return i===2?(e|=50331648,e&=-16777216,e|=De.fromColorRGB([r,n,o])):i===5&&(e&=-50331904,e|=33554432|r&255),e}_extractColor(e,i,r){let n=[0,0,-1,0,0,0],o=0,l=0;do{if(n[l+o]=e.params[i+l],e.hasSubParams(i+l)){let a=e.getSubParams(i+l),u=0;do n[1]===5&&(o=1),n[l+u+1+o]=a[u];while(++u=2||n[1]===2&&l+o>=5)break;n[1]&&(o=1)}while(++l+i5)&&(e=1),i.extended.underlineStyle=e,i.fg|=268435456,e===0&&(i.fg&=-268435457),i.updateExtended()}_processSGR0(e){e.fg=X.fg,e.bg=X.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 i=e.length,r,n=this._curAttrData;for(let o=0;o=30&&r<=37?(n.fg&=-50331904,n.fg|=16777216|r-30):r>=40&&r<=47?(n.bg&=-50331904,n.bg|=16777216|r-40):r>=90&&r<=97?(n.fg&=-50331904,n.fg|=16777216|r-90|8):r>=100&&r<=107?(n.bg&=-50331904,n.bg|=16777216|r-100|8):r===0?this._processSGR0(n):r===1?n.fg|=134217728:r===3?n.bg|=67108864:r===4?(n.fg|=268435456,this._processUnderline(e.hasSubParams(o)?e.getSubParams(o)[0]:1,n)):r===5?n.fg|=536870912:r===7?n.fg|=67108864:r===8?n.fg|=1073741824:r===9?n.fg|=2147483648:r===2?n.bg|=134217728:r===21?this._processUnderline(2,n):r===22?(n.fg&=-134217729,n.bg&=-134217729):r===23?n.bg&=-67108865:r===24?(n.fg&=-268435457,this._processUnderline(0,n)):r===25?n.fg&=-536870913:r===27?n.fg&=-67108865:r===28?n.fg&=-1073741825:r===29?n.fg&=2147483647:r===39?(n.fg&=-67108864,n.fg|=X.fg&16777215):r===49?(n.bg&=-67108864,n.bg|=X.bg&16777215):r===38||r===48||r===58?o+=this._extractColor(e,o,n):r===53?n.bg|=1073741824:r===55?n.bg&=-1073741825:r===59?(n.extended=n.extended.clone(),n.extended.underlineColor=-1,n.updateExtended()):r===100?(n.fg&=-67108864,n.fg|=X.fg&16777215,n.bg&=-67108864,n.bg|=X.bg&16777215):this._logService.debug("Unknown SGR attribute: %d.",r);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(`${b.ESC}[0n`);break;case 6:let i=this._activeBuffer.y+1,r=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${b.ESC}[${i};${r}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let i=this._activeBuffer.y+1,r=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${b.ESC}[?${i};${r}R`);break;case 15:break;case 25:break;case 26:break;case 53: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=X.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 i=e.length===0?1:e.params[0];if(i===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(i){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 r=i%2===1;this._coreService.decPrivateModes.cursorBlink=r}return!0}setScrollRegion(e){let i=e.params[0]||1,r;return(e.length<2||(r=e.params[1])>this._bufferService.rows||r===0)&&(r=this._bufferService.rows),r>i&&(this._activeBuffer.scrollTop=i-1,this._activeBuffer.scrollBottom=r-1,this._setCursor(0,0)),!0}windowOptions(e){if(!bl(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let i=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:i!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`${b.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(i===0||i===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>_l&&this._windowTitleStack.shift()),(i===0||i===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>_l&&this._iconNameStack.shift());break;case 23:(i===0||i===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(i===0||i===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,!0}restoreCursor(e){return 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,this._charsetService.charset=this._savedCharset,this._activeBuffer.savedCharset&&(this._charsetService.charset=this._activeBuffer.savedCharset),this._restrictCursor(),!0}setTitle(e){return this._windowTitle=e,this._onTitleChange.fire(e),!0}setIconName(e){return this._iconName=e,!0}setOrReportIndexedColor(e){let i=[],r=e.split(";");for(;r.length>1;){let n=r.shift(),o=r.shift();if(/^\d+$/.exec(n)){let l=parseInt(n);if(Sl(l))if(o==="?")i.push({type:0,index:l});else{let a=Ws(o);a&&i.push({type:1,index:l,color:a})}}}return i.length&&this._onColor.fire(i),!0}setHyperlink(e){let i=e.indexOf(";");if(i===-1)return!0;let r=e.slice(0,i).trim(),n=e.slice(i+1);return n?this._createHyperlink(r,n):r.trim()?!1:this._finishHyperlink()}_createHyperlink(e,i){this._getCurrentLinkId()&&this._finishHyperlink();let r=e.split(":"),n,o=r.findIndex(l=>l.startsWith("id="));return o!==-1&&(n=r[o].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:n,uri:i}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,i){let r=e.split(";");for(let n=0;n=this._specialColors.length);++n,++i)if(r[n]==="?")this._onColor.fire([{type:0,index:this._specialColors[i]}]);else{let o=Ws(r[n]);o&&this._onColor.fire([{type:1,index:this._specialColors[i],color:o}])}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 i=[],r=e.split(";");for(let n=0;n=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=X.clone(),this._eraseAttrDataInternal=X.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 q;e.content=1<<22|69,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let i=0;i(this._coreService.triggerDataEvent(`${b.ESC}${a}${b.ESC}\\`),!0),n=this._bufferService.buffer,o=this._optionsService.rawOptions,l={block:2,underline:4,bar:6};return r(e==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:e==='"p'?'P1$r61;1"p':e==="r"?`P1$r${n.scrollTop+1};${n.scrollBottom+1}r`:e==="m"?"P1$r0m":e===" q"?`P1$r${l[o.cursorStyle]-(o.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,i){this._dirtyRowTracker.markRangeDirty(e,i)}},Zi=class{constructor(t){this._bufferService=t;this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(t){tthis.end&&(this.end=t)}markRangeDirty(t,e){t>e&&(gl=t,t=e,e=gl),tthis.end&&(this.end=e)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};Zi=M([S(0,F)],Zi);function Sl(s){return 0<=s&&s<256}var _c=5e7,El=12,bc=50,gn=class extends D{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 v);this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}writeSync(e,i){if(i!==void 0&&this._syncCalls>i){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 r;for(;r=this._writeBuffer.shift();){this._action(r);let n=this._callbacks.shift();n&&n()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,i){if(this._pendingData>_c)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(i),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(i)}_innerWrite(e=0,i=!0){let r=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let n=this._writeBuffer[this._bufferOffset],o=this._action(n,i);if(o){let a=u=>performance.now()-r>=El?setTimeout(()=>this._innerWrite(0,u)):this._innerWrite(r,u);o.catch(u=>(queueMicrotask(()=>{throw u}),Promise.resolve(!1))).then(a);return}let l=this._callbacks[this._bufferOffset];if(l&&l(),this._bufferOffset++,this._pendingData-=n.length,performance.now()-r>=El)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>bc&&(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()}};var ui=class{constructor(t){this._bufferService=t;this._nextId=1;this._entriesWithId=new Map;this._dataByLinkId=new Map}registerLink(t){let e=this._bufferService.buffer;if(t.id===void 0){let a=e.addMarker(e.ybase+e.y),u={data:t,id:this._nextId++,lines:[a]};return a.onDispose(()=>this._removeMarkerFromLink(u,a)),this._dataByLinkId.set(u.id,u),u.id}let i=t,r=this._getEntryIdKey(i),n=this._entriesWithId.get(r);if(n)return this.addLineToLink(n.id,e.ybase+e.y),n.id;let o=e.addMarker(e.ybase+e.y),l={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[o]};return o.onDispose(()=>this._removeMarkerFromLink(l,o)),this._entriesWithId.set(l.key,l),this._dataByLinkId.set(l.id,l),l.id}addLineToLink(t,e){let i=this._dataByLinkId.get(t);if(i&&i.lines.every(r=>r.line!==e)){let r=this._bufferService.buffer.addMarker(e);i.lines.push(r),r.onDispose(()=>this._removeMarkerFromLink(i,r))}}getLinkData(t){return this._dataByLinkId.get(t)?.data}_getEntryIdKey(t){return`${t.id};;${t.uri}`}_removeMarkerFromLink(t,e){let i=t.lines.indexOf(e);i!==-1&&(t.lines.splice(i,1),t.lines.length===0&&(t.data.id!==void 0&&this._entriesWithId.delete(t.key),this._dataByLinkId.delete(t.id)))}};ui=M([S(0,F)],ui);var Tl=!1,Sn=class extends D{constructor(e){super();this._windowsWrappingHeuristics=this._register(new ye);this._onBinary=this._register(new v);this.onBinary=this._onBinary.event;this._onData=this._register(new v);this.onData=this._onData.event;this._onLineFeed=this._register(new v);this.onLineFeed=this._onLineFeed.event;this._onResize=this._register(new v);this.onResize=this._onResize.event;this._onWriteParsed=this._register(new v);this.onWriteParsed=this._onWriteParsed.event;this._onScroll=this._register(new v);this._instantiationService=new ln,this.optionsService=this._register(new dn(e)),this._instantiationService.setService(H,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(ni)),this._instantiationService.setService(F,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(ii)),this._instantiationService.setService(nr,this._logService),this.coreService=this._register(this._instantiationService.createInstance(li)),this._instantiationService.setService(ge,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(ai)),this._instantiationService.setService(rr,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(Ae)),this._instantiationService.setService(Js,this.unicodeService),this._charsetService=this._instantiationService.createInstance(pn),this._instantiationService.setService(Zs,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(ui),this._instantiationService.setService(sr,this._oscLinkService),this._inputHandler=this._register(new vn(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register($.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(this._inputHandler),this._register($.forward(this._bufferService.onResize,this._onResize)),this._register($.forward(this.coreService.onData,this._onData)),this._register($.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(["windowsMode","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 gn((i,r)=>this._inputHandler.parse(i,r))),this._register($.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new v),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 i in e)this.optionsService.options[i]=e[i]}write(e,i){this._writeBuffer.write(e,i)}writeSync(e,i){this._logService.logLevel<=3&&!Tl&&(this._logService.warn("writeSync is unreliable and will be removed soon."),Tl=!0),this._writeBuffer.writeSync(e,i)}input(e,i=!0){this.coreService.triggerDataEvent(e,i)}resize(e,i){isNaN(e)||isNaN(i)||(e=Math.max(e,ks),i=Math.max(i,Cs),this._bufferService.resize(e,i))}scroll(e,i=!1){this._bufferService.scroll(e,i)}scrollLines(e,i){this._bufferService.scrollLines(e,i)}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 i=e-this._bufferService.buffer.ydisp;i!==0&&this.scrollLines(i)}registerEscHandler(e,i){return this._inputHandler.registerEscHandler(e,i)}registerDcsHandler(e,i){return this._inputHandler.registerDcsHandler(e,i)}registerCsiHandler(e,i){return this._inputHandler.registerCsiHandler(e,i)}registerOscHandler(e,i){return this._inputHandler.registerOscHandler(e,i)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,i=this.optionsService.rawOptions.windowsPty;i&&i.buildNumber!==void 0&&i.buildNumber!==void 0?e=i.backend==="conpty"&&i.buildNumber<21376:this.optionsService.rawOptions.windowsMode&&(e=!0),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Bs.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>(Bs(this._bufferService),!1))),this._windowsWrappingHeuristics.value=C(()=>{for(let i of e)i.dispose()})}}};var gc={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};function Il(s,t,e,i){let r={type:0,cancel:!1,key:void 0},n=(s.shiftKey?1:0)|(s.altKey?2:0)|(s.ctrlKey?4:0)|(s.metaKey?8:0);switch(s.keyCode){case 0:s.key==="UIKeyInputUpArrow"?t?r.key=b.ESC+"OA":r.key=b.ESC+"[A":s.key==="UIKeyInputLeftArrow"?t?r.key=b.ESC+"OD":r.key=b.ESC+"[D":s.key==="UIKeyInputRightArrow"?t?r.key=b.ESC+"OC":r.key=b.ESC+"[C":s.key==="UIKeyInputDownArrow"&&(t?r.key=b.ESC+"OB":r.key=b.ESC+"[B");break;case 8:r.key=s.ctrlKey?"\b":b.DEL,s.altKey&&(r.key=b.ESC+r.key);break;case 9:if(s.shiftKey){r.key=b.ESC+"[Z";break}r.key=b.HT,r.cancel=!0;break;case 13:r.key=s.altKey?b.ESC+b.CR:b.CR,r.cancel=!0;break;case 27:r.key=b.ESC,s.altKey&&(r.key=b.ESC+b.ESC),r.cancel=!0;break;case 37:if(s.metaKey)break;n?r.key=b.ESC+"[1;"+(n+1)+"D":t?r.key=b.ESC+"OD":r.key=b.ESC+"[D";break;case 39:if(s.metaKey)break;n?r.key=b.ESC+"[1;"+(n+1)+"C":t?r.key=b.ESC+"OC":r.key=b.ESC+"[C";break;case 38:if(s.metaKey)break;n?r.key=b.ESC+"[1;"+(n+1)+"A":t?r.key=b.ESC+"OA":r.key=b.ESC+"[A";break;case 40:if(s.metaKey)break;n?r.key=b.ESC+"[1;"+(n+1)+"B":t?r.key=b.ESC+"OB":r.key=b.ESC+"[B";break;case 45:!s.shiftKey&&!s.ctrlKey&&(r.key=b.ESC+"[2~");break;case 46:n?r.key=b.ESC+"[3;"+(n+1)+"~":r.key=b.ESC+"[3~";break;case 36:n?r.key=b.ESC+"[1;"+(n+1)+"H":t?r.key=b.ESC+"OH":r.key=b.ESC+"[H";break;case 35:n?r.key=b.ESC+"[1;"+(n+1)+"F":t?r.key=b.ESC+"OF":r.key=b.ESC+"[F";break;case 33:s.shiftKey?r.type=2:s.ctrlKey?r.key=b.ESC+"[5;"+(n+1)+"~":r.key=b.ESC+"[5~";break;case 34:s.shiftKey?r.type=3:s.ctrlKey?r.key=b.ESC+"[6;"+(n+1)+"~":r.key=b.ESC+"[6~";break;case 112:n?r.key=b.ESC+"[1;"+(n+1)+"P":r.key=b.ESC+"OP";break;case 113:n?r.key=b.ESC+"[1;"+(n+1)+"Q":r.key=b.ESC+"OQ";break;case 114:n?r.key=b.ESC+"[1;"+(n+1)+"R":r.key=b.ESC+"OR";break;case 115:n?r.key=b.ESC+"[1;"+(n+1)+"S":r.key=b.ESC+"OS";break;case 116:n?r.key=b.ESC+"[15;"+(n+1)+"~":r.key=b.ESC+"[15~";break;case 117:n?r.key=b.ESC+"[17;"+(n+1)+"~":r.key=b.ESC+"[17~";break;case 118:n?r.key=b.ESC+"[18;"+(n+1)+"~":r.key=b.ESC+"[18~";break;case 119:n?r.key=b.ESC+"[19;"+(n+1)+"~":r.key=b.ESC+"[19~";break;case 120:n?r.key=b.ESC+"[20;"+(n+1)+"~":r.key=b.ESC+"[20~";break;case 121:n?r.key=b.ESC+"[21;"+(n+1)+"~":r.key=b.ESC+"[21~";break;case 122:n?r.key=b.ESC+"[23;"+(n+1)+"~":r.key=b.ESC+"[23~";break;case 123:n?r.key=b.ESC+"[24;"+(n+1)+"~":r.key=b.ESC+"[24~";break;default:if(s.ctrlKey&&!s.shiftKey&&!s.altKey&&!s.metaKey)s.keyCode>=65&&s.keyCode<=90?r.key=String.fromCharCode(s.keyCode-64):s.keyCode===32?r.key=b.NUL:s.keyCode>=51&&s.keyCode<=55?r.key=String.fromCharCode(s.keyCode-51+27):s.keyCode===56?r.key=b.DEL:s.keyCode===219?r.key=b.ESC:s.keyCode===220?r.key=b.FS:s.keyCode===221&&(r.key=b.GS);else if((!e||i)&&s.altKey&&!s.metaKey){let l=gc[s.keyCode]?.[s.shiftKey?1:0];if(l)r.key=b.ESC+l;else if(s.keyCode>=65&&s.keyCode<=90){let a=s.ctrlKey?s.keyCode-64:s.keyCode+32,u=String.fromCharCode(a);s.shiftKey&&(u=u.toUpperCase()),r.key=b.ESC+u}else if(s.keyCode===32)r.key=b.ESC+(s.ctrlKey?b.NUL:" ");else if(s.key==="Dead"&&s.code.startsWith("Key")){let a=s.code.slice(3,4);s.shiftKey||(a=a.toLowerCase()),r.key=b.ESC+a,r.cancel=!0}}else e&&!s.altKey&&!s.ctrlKey&&!s.shiftKey&&s.metaKey?s.keyCode===65&&(r.type=1):s.key&&!s.ctrlKey&&!s.altKey&&!s.metaKey&&s.keyCode>=48&&s.key.length===1?r.key=s.key:s.key&&s.ctrlKey&&(s.key==="_"&&(r.key=b.US),s.key==="@"&&(r.key=b.NUL));break}return r}var ee=0,En=class{constructor(t){this._getKey=t;this._array=[];this._insertedValues=[];this._flushInsertedTask=new Jt;this._isFlushingInserted=!1;this._deletedIndices=[];this._flushDeletedTask=new Jt;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(t){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(t)}_flushInserted(){let t=this._insertedValues.sort((n,o)=>this._getKey(n)-this._getKey(o)),e=0,i=0,r=new Array(this._array.length+this._insertedValues.length);for(let n=0;n=this._array.length||this._getKey(t[e])<=this._getKey(this._array[i])?(r[n]=t[e],e++):r[n]=this._array[i++];this._array=r,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(t){if(this._flushCleanupInserted(),this._array.length===0)return!1;let e=this._getKey(t);if(e===void 0||(ee=this._search(e),ee===-1)||this._getKey(this._array[ee])!==e)return!1;do if(this._array[ee]===t)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(ee),!0;while(++een-o),e=0,i=new Array(this._array.length-t.length),r=0;for(let n=0;n0&&this._flushDeletedTask.flush()}*getKeyIterator(t){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(ee=this._search(t),!(ee<0||ee>=this._array.length)&&this._getKey(this._array[ee])===t))do yield this._array[ee];while(++ee=this._array.length)&&this._getKey(this._array[ee])===t))do e(this._array[ee]);while(++ee=e;){let r=e+i>>1,n=this._getKey(this._array[r]);if(n>t)i=r-1;else if(n0&&this._getKey(this._array[r-1])===t;)r--;return r}}return e}};var Us=0,yl=0,Tn=class extends D{constructor(){super();this._decorations=new En(e=>e?.marker.line);this._onDecorationRegistered=this._register(new v);this.onDecorationRegistered=this._onDecorationRegistered.event;this._onDecorationRemoved=this._register(new v);this.onDecorationRemoved=this._onDecorationRemoved.event;this._register(C(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let i=new Ks(e);if(i){let r=i.marker.onDispose(()=>i.dispose()),n=i.onDispose(()=>{n.dispose(),i&&(this._decorations.delete(i)&&this._onDecorationRemoved.fire(i),r.dispose())});this._decorations.insert(i),this._onDecorationRegistered.fire(i)}return i}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,i,r){let n=0,o=0;for(let l of this._decorations.getKeyIterator(i))n=l.options.x??0,o=n+(l.options.width??1),e>=n&&e{Us=o.options.x??0,yl=Us+(o.options.width??1),e>=Us&&e=this._debounceThresholdMS)this._lastRefreshMs=r,this._innerRefresh();else if(!this._additionalRefreshRequested){let n=r-this._lastRefreshMs,o=this._debounceThresholdMS-n;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},o)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let t=Math.max(this._rowStart,0),e=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(t,e)}};var xl=20;var wl=!1,Tt=class extends D{constructor(e,i,r,n){super();this._terminal=e;this._coreBrowserService=r;this._renderService=n;this._rowColumns=new WeakMap;this._liveRegionLineCount=0;this._charsToConsume=[];this._charsToAnnounce="";let o=this._coreBrowserService.mainDocument;this._accessibilityContainer=o.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=o.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let l=0;lthis._handleBoundaryFocus(l,0),this._bottomBoundaryFocusListener=l=>this._handleBoundaryFocus(l,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=o.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new In(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");wl?(this._accessibilityContainer.classList.add("debug"),this._rowContainer.classList.add("debug"),this._debugRootContainer=o.createElement("div"),this._debugRootContainer.classList.add("xterm"),this._debugRootContainer.appendChild(o.createTextNode("------start a11y------")),this._debugRootContainer.appendChild(this._accessibilityContainer),this._debugRootContainer.appendChild(o.createTextNode("------end a11y------")),this._terminal.element.insertAdjacentElement("afterend",this._debugRootContainer)):this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(l=>this._handleResize(l.rows))),this._register(this._terminal.onRender(l=>this._refreshRows(l.start,l.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(l=>this._handleChar(l))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` +`))),this._register(this._terminal.onA11yTab(l=>this._handleTab(l))),this._register(this._terminal.onKey(l=>this._handleKey(l.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(L(o,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(C(()=>{wl?this._debugRootContainer.remove():this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let i=0;i0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` +`&&(this._liveRegionLineCount++,this._liveRegionLineCount===xl+1&&(this._liveRegion.textContent+=_i.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,i){this._liveRegionDebouncer.refresh(e,i,this._terminal.rows)}_renderRows(e,i){let r=this._terminal.buffer,n=r.lines.length.toString();for(let o=e;o<=i;o++){let l=r.lines.get(r.ydisp+o),a=[],u=l?.translateToString(!0,void 0,void 0,a)||"",h=(r.ydisp+o+1).toString(),c=this._rowElements[o];c&&(u.length===0?(c.textContent="\xA0",this._rowColumns.set(c,[0,1])):(c.textContent=u,this._rowColumns.set(c,a)),c.setAttribute("aria-posinset",h),c.setAttribute("aria-setsize",n),this._alignRowWidth(c))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,i){let r=e.target,n=this._rowElements[i===0?1:this._rowElements.length-2],o=r.getAttribute("aria-posinset"),l=i===0?"1":`${this._terminal.buffer.lines.length}`;if(o===l||e.relatedTarget!==n)return;let a,u;if(i===0?(a=r,u=this._rowElements.pop(),this._rowContainer.removeChild(u)):(a=this._rowElements.shift(),u=r,this._rowContainer.removeChild(a)),a.removeEventListener("focus",this._topBoundaryFocusListener),u.removeEventListener("focus",this._bottomBoundaryFocusListener),i===0){let h=this._createAccessibilityTreeNode();this._rowElements.unshift(h),this._rowContainer.insertAdjacentElement("afterbegin",h)}else{let h=this._createAccessibilityTreeNode();this._rowElements.push(h),this._rowContainer.appendChild(h)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(i===0?-1:1),this._rowElements[i===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 i={node:e.anchorNode,offset:e.anchorOffset},r={node:e.focusNode,offset:e.focusOffset};if((i.node.compareDocumentPosition(r.node)&Node.DOCUMENT_POSITION_PRECEDING||i.node===r.node&&i.offset>r.offset)&&([i,r]=[r,i]),i.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(i={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(i.node))return;let n=this._rowElements.slice(-1)[0];if(r.node.compareDocumentPosition(n)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(r={node:n,offset:n.textContent?.length??0}),!this._rowContainer.contains(r.node))return;let o=({node:u,offset:h})=>{let c=u instanceof Text?u.parentNode:u,d=parseInt(c?.getAttribute("aria-posinset"),10)-1;if(isNaN(d))return console.warn("row is invalid. Race condition?"),null;let _=this._rowColumns.get(c);if(!_)return console.warn("columns is null. Race condition?"),null;let p=h<_.length?_[h]:_.slice(-1)[0]+1;return p>=this._terminal.cols&&(++d,p=0),{row:d,column:p}},l=o(i),a=o(r);if(!(!l||!a)){if(l.row>a.row||l.row===a.row&&l.column>=a.column)throw new Error("invalid range");this._terminal.select(l.column,l.row,(a.row-l.row)*this._terminal.cols-l.column+a.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let i=this._rowContainer.children.length;ie;)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{Ne(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(L(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(L(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register(L(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register(L(this._element,"mouseup",this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let i=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!i)return;this._isMouseOut=!1;let r=e.composedPath();for(let n=0;n{n?.forEach(o=>{o.link.dispose&&o.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let r=!1;for(let[n,o]of this._linkProviderService.linkProviders.entries())i?this._activeProviderReplies?.get(n)&&(r=this._checkLinkProviderResult(n,e,r)):o.provideLinks(e.y,l=>{if(this._isMouseOut)return;let a=l?.map(u=>({link:u}));this._activeProviderReplies?.set(n,a),r=this._checkLinkProviderResult(n,e,r),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,i){let r=new Set;for(let n=0;ne?this._bufferService.cols:a.link.range.end.x;for(let c=u;c<=h;c++){if(r.has(c)){o.splice(l--,1);break}r.add(c)}}}}_checkLinkProviderResult(e,i,r){if(!this._activeProviderReplies)return r;let n=this._activeProviderReplies.get(e),o=!1;for(let l=0;lthis._linkAtPosition(a.link,i));l&&(r=!0,this._handleNewLink(l))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!r)for(let l=0;lthis._linkAtPosition(u.link,i));if(a){r=!0,this._handleNewLink(a);break}}return r}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let i=this._positionFromMouseEvent(e,this._element,this._mouseService);i&&this._mouseDownLink&&Ec(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,i)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,i){!this._currentLink||!this._lastMouseEvent||(!e||!i||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=i)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Ne(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let i=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);i&&this._linkAtPosition(e.link,i)&&(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:r=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==r&&(this._currentLink.state.decorations.pointerCursor=r,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",r))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:r=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==r&&(this._currentLink.state.decorations.underline=r,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,r))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(r=>{if(!this._currentLink)return;let n=r.start===0?0:r.start+1+this._bufferService.buffer.ydisp,o=this._bufferService.buffer.ydisp+1+r.end;if(this._currentLink.link.range.start.y>=n&&this._currentLink.link.range.end.y<=o&&(this._clearCurrentLink(n,o),this._lastMouseEvent)){let l=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);l&&this._askForLink(l,!1)}})))}_linkHover(e,i,r){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(i,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),i.hover&&i.hover(r,i.text)}_fireUnderlineEvent(e,i){let r=e.range,n=this._bufferService.buffer.ydisp,o=this._createLinkUnderlineEvent(r.start.x-1,r.start.y-n-1,r.end.x,r.end.y-n-1,void 0);(i?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(o)}_linkLeave(e,i,r){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(i,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),i.leave&&i.leave(r,i.text)}_linkAtPosition(e,i){let r=e.range.start.y*this._bufferService.cols+e.range.start.x,n=e.range.end.y*this._bufferService.cols+e.range.end.x,o=i.y*this._bufferService.cols+i.x;return r<=o&&o<=n}_positionFromMouseEvent(e,i,r){let n=r.getCoords(e,i,this._bufferService.cols,this._bufferService.rows);if(n)return{x:n[0],y:n[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,i,r,n,o){return{x1:e,y1:i,x2:r,y2:n,cols:this._bufferService.cols,fg:o}}};hi=M([S(1,Dt),S(2,ce),S(3,F),S(4,lr)],hi);function Ec(s,t){return s.text===t.text&&s.range.start.x===t.range.start.x&&s.range.start.y===t.range.start.y&&s.range.end.x===t.range.end.x&&s.range.end.y===t.range.end.y}var yn=class extends Sn{constructor(e={}){super(e);this._linkifier=this._register(new ye);this.browser=tn;this._keyDownHandled=!1;this._keyDownSeen=!1;this._keyPressHandled=!1;this._unprocessedDeadKey=!1;this._accessibilityManager=this._register(new ye);this._onCursorMove=this._register(new v);this.onCursorMove=this._onCursorMove.event;this._onKey=this._register(new v);this.onKey=this._onKey.event;this._onRender=this._register(new v);this.onRender=this._onRender.event;this._onSelectionChange=this._register(new v);this.onSelectionChange=this._onSelectionChange.event;this._onTitleChange=this._register(new v);this.onTitleChange=this._onTitleChange.event;this._onBell=this._register(new v);this.onBell=this._onBell.event;this._onFocus=this._register(new v);this._onBlur=this._register(new v);this._onA11yCharEmitter=this._register(new v);this._onA11yTabEmitter=this._register(new v);this._onWillOpen=this._register(new v);this._setup(),this._decorationService=this._instantiationService.createInstance(Tn),this._instantiationService.setService(Be,this._decorationService),this._linkProviderService=this._instantiationService.createInstance(Qr),this._instantiationService.setService(lr,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(wt)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(i=>this.refresh(i?.start??0,i?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(i=>this._reportWindowsOptions(i))),this._register(this._inputHandler.onColor(i=>this._handleColorEvent(i))),this._register($.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register($.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register($.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register($.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(i=>this._afterResize(i.cols,i.rows))),this._register(C(()=>{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}_handleColorEvent(e){if(this._themeService)for(let i of e){let r,n="";switch(i.index){case 256:r="foreground",n="10";break;case 257:r="background",n="11";break;case 258:r="cursor",n="12";break;default:r="ansi",n="4;"+i.index}switch(i.type){case 0:let o=U.toColorRGB(r==="ansi"?this._themeService.colors.ansi[i.index]:this._themeService.colors[r]);this.coreService.triggerDataEvent(`${b.ESC}]${n};${ml(o)}${fs.ST}`);break;case 1:if(r==="ansi")this._themeService.modifyColors(l=>l.ansi[i.index]=j.toColor(...i.color));else{let l=r;this._themeService.modifyColors(a=>a[l]=j.toColor(...i.color))}break;case 2:this._themeService.restoreColor(i.index);break}}}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(Tt,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(b.ESC+"[I"),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(b.ESC+"[O"),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;let e=this.buffer.ybase+this.buffer.y,i=this.buffer.lines.get(e);if(!i)return;let r=Math.min(this.buffer.x,this.cols-1),n=this._renderService.dimensions.css.cell.height,o=i.getWidth(r),l=this._renderService.dimensions.css.cell.width*o,a=this.buffer.y*this._renderService.dimensions.css.cell.height,u=r*this._renderService.dimensions.css.cell.width;this.textarea.style.left=u+"px",this.textarea.style.top=a+"px",this.textarea.style.width=l+"px",this.textarea.style.height=n+"px",this.textarea.style.lineHeight=n+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this._register(L(this.element,"copy",i=>{this.hasSelection()&&Vs(i,this._selectionService)}));let e=i=>qs(i,this.textarea,this.coreService,this.optionsService);this._register(L(this.textarea,"paste",e)),this._register(L(this.element,"paste",e)),Ss?this._register(L(this.element,"mousedown",i=>{i.button===2&&Pn(i,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(L(this.element,"contextmenu",i=>{Pn(i,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),Bi&&this._register(L(this.element,"auxclick",i=>{i.button===1&&Mn(i,this.textarea,this.screenElement)}))}_bindKeys(){this._register(L(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register(L(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register(L(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register(L(this.textarea,"compositionstart",()=>this._compositionHelper.compositionstart())),this._register(L(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register(L(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register(L(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"),e.appendChild(this.element);let i=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),i.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register(L(this.screenElement,"mousemove",o=>this.updateCursorStyle(o))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),i.appendChild(this.screenElement);let r=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",mi.get()),Ts||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",()=>r.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(Jr,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<"u"?window.document:null)),this._instantiationService.setService(ae,this._coreBrowserService),this._register(L(this.textarea,"focus",o=>this._handleTextAreaFocus(o))),this._register(L(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(jt,this._document,this._helperContainer),this._instantiationService.setService(nt,this._charSizeService),this._themeService=this._instantiationService.createInstance(ti),this._instantiationService.setService(Re,this._themeService),this._characterJoinerService=this._instantiationService.createInstance(ct),this._instantiationService.setService(or,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(Qt,this.rows,this.screenElement)),this._instantiationService.setService(ce,this._renderService),this._register(this._renderService.onRenderedViewportChange(o=>this._onRender.fire(o))),this.onResize(o=>this._renderService.resize(o.cols,o.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance($t,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(Xt),this._instantiationService.setService(Dt,this._mouseService);let n=this._linkifier.value=this._register(this._instantiationService.createInstance(hi,this.screenElement));this.element.appendChild(i);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(zt,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(o=>{super.scrollLines(o,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(ei,this.element,this.screenElement,n)),this._instantiationService.setService(Qs,this._selectionService),this._register(this._selectionService.onRequestScrollLines(o=>this.scrollLines(o.amount,o.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(o=>this._renderService.handleSelectionChanged(o.start,o.end,o.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(o=>{this.textarea.value=o,this.textarea.focus(),this.textarea.select()})),this._register($.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(Gt,this.screenElement)),this._register(L(this.element,"mousedown",o=>this._selectionService.handleMouseDown(o))),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(Tt,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",o=>this._handleScreenReaderModeOptionChange(o))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(bt,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRuler",o=>{!this._overviewRulerRenderer&&o&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(bt,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(Yt,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,i=this.element;function r(l){let a=e._mouseService.getMouseReportCoords(l,e.screenElement);if(!a)return!1;let u,h;switch(l.overrideType||l.type){case"mousemove":h=32,l.buttons===void 0?(u=3,l.button!==void 0&&(u=l.button<3?l.button:3)):u=l.buttons&1?0:l.buttons&4?1:l.buttons&2?2:3;break;case"mouseup":h=0,u=l.button<3?l.button:3;break;case"mousedown":h=1,u=l.button<3?l.button:3;break;case"wheel":if(e._customWheelEventHandler&&e._customWheelEventHandler(l)===!1)return!1;let c=l.deltaY;if(c===0||e.coreMouseService.consumeWheelEvent(l,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;h=c<0?0:1,u=4;break;default:return!1}return h===void 0||u===void 0||u>4?!1:e.coreMouseService.triggerMouseEvent({col:a.col,row:a.row,x:a.x,y:a.y,button:u,action:h,ctrl:l.ctrlKey,alt:l.altKey,shift:l.shiftKey})}let n={mouseup:null,wheel:null,mousedrag:null,mousemove:null},o={mouseup:l=>(r(l),l.buttons||(this._document.removeEventListener("mouseup",n.mouseup),n.mousedrag&&this._document.removeEventListener("mousemove",n.mousedrag)),this.cancel(l)),wheel:l=>(r(l),this.cancel(l,!0)),mousedrag:l=>{l.buttons&&r(l)},mousemove:l=>{l.buttons||r(l)}};this._register(this.coreMouseService.onProtocolChange(l=>{l?(this.optionsService.rawOptions.logLevel==="debug"&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(l)),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()),l&8?n.mousemove||(i.addEventListener("mousemove",o.mousemove),n.mousemove=o.mousemove):(i.removeEventListener("mousemove",n.mousemove),n.mousemove=null),l&16?n.wheel||(i.addEventListener("wheel",o.wheel,{passive:!1}),n.wheel=o.wheel):(i.removeEventListener("wheel",n.wheel),n.wheel=null),l&2?n.mouseup||(n.mouseup=o.mouseup):(this._document.removeEventListener("mouseup",n.mouseup),n.mouseup=null),l&4?n.mousedrag||(n.mousedrag=o.mousedrag):(this._document.removeEventListener("mousemove",n.mousedrag),n.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(L(i,"mousedown",l=>{if(l.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(l)))return r(l),n.mouseup&&this._document.addEventListener("mouseup",n.mouseup),n.mousedrag&&this._document.addEventListener("mousemove",n.mousedrag),this.cancel(l)})),this._register(L(i,"wheel",l=>{if(!n.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(l)===!1)return!1;if(!this.buffer.hasScrollback){if(l.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(l,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return this.cancel(l,!0);let h=b.ESC+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(l.deltaY<0?"A":"B");return this.coreService.triggerDataEvent(h,!0),this.cancel(l,!0)}}},{passive:!1}))}refresh(e,i){this._renderService?.refreshRows(e,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,i){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,i),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 i=e-this._bufferService.buffer.ydisp;i!==0&&this.scrollLines(i)}paste(e){Cn(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 i=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),i}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,i,r){this._selectionService.setSelection(e,i,r)}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,i){this._selectionService?.selectLines(e,i)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let i=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!i&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!i&&(e.key==="Dead"||e.key==="AltGraph")&&(this._unprocessedDeadKey=!0);let r=Il(e,this.coreService.decPrivateModes.applicationCursorKeys,this.browser.isMac,this.options.macOptionIsMeta);if(this.updateCursorStyle(e),r.type===3||r.type===2){let n=this.rows-1;return this.scrollLines(r.type===2?-n:n),this.cancel(e,!0)}if(r.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(r.cancel&&this.cancel(e,!0),!r.key)||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;if((r.key===b.ETX||r.key===b.CR)&&(this.textarea.value=""),this._onKey.fire({key:r.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(r.key,!0),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return this.cancel(e,!0);this._keyDownHandled=!0}_isThirdLevelShift(e,i){let r=e.isMac&&!this.options.macOptionIsMeta&&i.altKey&&!i.ctrlKey&&!i.metaKey||e.isWindows&&i.altKey&&i.ctrlKey&&!i.metaKey||e.isWindows&&i.getModifierState("AltGraph");return i.type==="keypress"?r:r&&(!i.keyCode||i.keyCode>47)}_keyUp(e){this._keyDownSeen=!1,!(this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)&&(Tc(e)||this.focus(),this.updateCursorStyle(e),this._keyPressHandled=!1)}_keyPress(e){let i;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(this.cancel(e),e.charCode)i=e.charCode;else if(e.which===null||e.which===void 0)i=e.keyCode;else if(e.which!==0&&e.charCode!==0)i=e.which;else return!1;return!i||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(i=String.fromCharCode(i),this._onKey.fire({key:i,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i,!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 i=e.data;return this.coreService.triggerDataEvent(i,!0),this.cancel(e),!0}return!1}resize(e,i){if(e===this.cols&&i===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,i)}_afterResize(e,i){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;t--)this._addons[t].instance.dispose()}loadAddon(t,e){let i={instance:e,dispose:e.dispose,isDisposed:!1};this._addons.push(i),e.dispose=()=>this._wrappedAddonDispose(i),e.activate(t)}_wrappedAddonDispose(t){if(t.isDisposed)return;let e=-1;for(let i=0;i=this._line.length))return e?(this._line.loadCell(t,e),e):this._line.loadCell(t,new q)}translateToString(t,e,i){return this._line.translateToString(t,e,i)}};var Ji=class{constructor(t,e){this._buffer=t;this.type=e}init(t){return this._buffer=t,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(t){let e=this._buffer.lines.get(t);if(e)return new wn(e)}getNullCell(){return new q}};var Dn=class extends D{constructor(e){super();this._core=e;this._onBufferChange=this._register(new v);this.onBufferChange=this._onBufferChange.event;this._normal=new Ji(this._core.buffers.normal,"normal"),this._alternate=new Ji(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)}};var Rn=class{constructor(t){this._core=t}registerCsiHandler(t,e){return this._core.registerCsiHandler(t,i=>e(i.toArray()))}addCsiHandler(t,e){return this.registerCsiHandler(t,e)}registerDcsHandler(t,e){return this._core.registerDcsHandler(t,(i,r)=>e(i,r.toArray()))}addDcsHandler(t,e){return this.registerDcsHandler(t,e)}registerEscHandler(t,e){return this._core.registerEscHandler(t,e)}addEscHandler(t,e){return this.registerEscHandler(t,e)}registerOscHandler(t,e){return this._core.registerOscHandler(t,e)}addOscHandler(t,e){return this.registerOscHandler(t,e)}};var Ln=class{constructor(t){this._core=t}register(t){this._core.unicodeService.register(t)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(t){this._core.unicodeService.activeVersion=t}};var Ic=["cols","rows"],Ue=0,Dl=class extends D{constructor(t){super(),this._core=this._register(new yn(t)),this._addonManager=this._register(new xn),this._publicOptions={...this._core.options};let e=r=>this._core.options[r],i=(r,n)=>{this._checkReadonlyOptions(r),this._core.options[r]=n};for(let r in this._core.options){let n={get:e.bind(this,r),set:i.bind(this,r)};Object.defineProperty(this._publicOptions,r,n)}}_checkReadonlyOptions(t){if(Ic.includes(t))throw new Error(`Option "${t}" 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 element(){return this._core.element}get parser(){return this._parser||(this._parser=new Rn(this._core)),this._parser}get unicode(){return this._checkProposedApi(),new Ln(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 Dn(this._core))),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){let t=this._core.coreService.decPrivateModes,e="none";switch(this._core.coreMouseService.activeProtocol){case"X10":e="x10";break;case"VT200":e="vt200";break;case"DRAG":e="drag";break;case"ANY":e="any";break}return{applicationCursorKeysMode:t.applicationCursorKeys,applicationKeypadMode:t.applicationKeypad,bracketedPasteMode:t.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:e,originMode:t.origin,reverseWraparoundMode:t.reverseWraparound,sendFocusMode:t.sendFocus,synchronizedOutputMode:t.synchronizedOutput,wraparoundMode:t.wraparound}}get options(){return this._publicOptions}set options(t){for(let e in t)this._publicOptions[e]=t[e]}blur(){this._core.blur()}focus(){this._core.focus()}input(t,e=!0){this._core.input(t,e)}resize(t,e){this._verifyIntegers(t,e),this._core.resize(t,e)}open(t){this._core.open(t)}attachCustomKeyEventHandler(t){this._core.attachCustomKeyEventHandler(t)}attachCustomWheelEventHandler(t){this._core.attachCustomWheelEventHandler(t)}registerLinkProvider(t){return this._core.registerLinkProvider(t)}registerCharacterJoiner(t){return this._checkProposedApi(),this._core.registerCharacterJoiner(t)}deregisterCharacterJoiner(t){this._checkProposedApi(),this._core.deregisterCharacterJoiner(t)}registerMarker(t=0){return this._verifyIntegers(t),this._core.registerMarker(t)}registerDecoration(t){return this._checkProposedApi(),this._verifyPositiveIntegers(t.x??0,t.width??0,t.height??0),this._core.registerDecoration(t)}hasSelection(){return this._core.hasSelection()}select(t,e,i){this._verifyIntegers(t,e,i),this._core.select(t,e,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(t,e){this._verifyIntegers(t,e),this._core.selectLines(t,e)}dispose(){super.dispose()}scrollLines(t){this._verifyIntegers(t),this._core.scrollLines(t)}scrollPages(t){this._verifyIntegers(t),this._core.scrollPages(t)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(t){this._verifyIntegers(t),this._core.scrollToLine(t)}clear(){this._core.clear()}write(t,e){this._core.write(t,e)}writeln(t,e){this._core.write(t),this._core.write(`\r +`,e)}paste(t){this._core.paste(t)}refresh(t,e){this._verifyIntegers(t,e),this._core.refresh(t,e)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(t){this._addonManager.loadAddon(this,t)}static get strings(){return{get promptLabel(){return mi.get()},set promptLabel(t){mi.set(t)},get tooMuchOutput(){return _i.get()},set tooMuchOutput(t){_i.set(t)}}}_verifyIntegers(...t){for(Ue of t)if(Ue===1/0||isNaN(Ue)||Ue%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...t){for(Ue of t)if(Ue&&(Ue===1/0||isNaN(Ue)||Ue%1!==0||Ue<0))throw new Error("This API only accepts positive integers")}};export{Dl as Terminal}; +//# sourceMappingURL=xterm.mjs.map diff --git a/Glint/WebRemote/WebRemoteProtocol.swift b/Glint/WebRemote/WebRemoteProtocol.swift new file mode 100644 index 0000000..2aa3b2a --- /dev/null +++ b/Glint/WebRemote/WebRemoteProtocol.swift @@ -0,0 +1,648 @@ +import CryptoKit +import Darwin +import Foundation +import Security + +enum WebRemoteTerminalSnapshotResult { + case success(WebRemoteTerminalSnapshot) + case failure(String) +} + +struct WebRemoteTerminalSnapshot { + let payload: Data + let outputSequence: UInt64 +} + +enum WebRemoteOpenProjectResult { + case success(UUID) + case failure(String) +} + +enum WebRemoteCreateTerminalResult { + case success(String) + case failure(String) +} + +enum WebRemoteCloseTerminalResult: Equatable { + case success + case confirmationRequired + case failure(String) +} + +struct WebRemoteTerminalSize: Equatable { + static let columnRange = 20 ... 500 + static let rowRange = 5 ... 200 + + let columns: Int + let rows: Int + + static func parse(_ object: [String: Any]) -> WebRemoteTerminalSize? { + guard let columns = object["columns"] as? Int, + let rows = object["rows"] as? Int, + columnRange.contains(columns), + rowRange.contains(rows) + else { return nil } + return WebRemoteTerminalSize(columns: columns, rows: rows) + } +} + +enum WebRemoteSnapshotPayload { + private static let resetScreen = "\u{1b}[0m\u{1b}[2J\u{1b}[H" + + static func make(ansi: String?, state: WebRemoteTerminalState? = nil) -> Data { + let terminalANSI = ansi?.replacingOccurrences(of: "\n", with: "\r\n") ?? "" + guard let state else { return Data((resetScreen + terminalANSI).utf8) } + + var payload = state.activeScreen == .alternate ? "\u{1b}[?1049h" : "" + payload += resetScreen + payload += terminalANSI + + for mode in state.modes where mode.code > 0 { + payload += "\u{1b}[" + if !mode.ansi { payload += "?" } + payload += "\(mode.code)\(mode.on ? "h" : "l")" + } + + let region = state.scrollingRegion + if let region, region.isValid(columns: state.columns, rows: state.rows) { + if region.top != 0 || region.bottom != state.rows - 1 { + payload += "\u{1b}[\(region.top + 1);\(region.bottom + 1)r" + } + if region.left != 0 || region.right != state.columns - 1 { + payload += "\u{1b}[\(region.left + 1);\(region.right + 1)s" + } + } + + let originMode = state.modes.contains { !$0.ansi && $0.code == 6 && $0.on } + let cursorRow = originMode ? state.cursor.row - (region?.top ?? 0) : state.cursor.row + let cursorColumn = originMode ? state.cursor.column - (region?.left ?? 0) : state.cursor.column + payload += "\u{1b}[\(max(cursorRow, 0) + 1);\(max(cursorColumn, 0) + 1)H" + payload += state.cursor.style.sequence(blinking: state.cursor.blinking) + payload += state.cursor.visible ? "\u{1b}[?25h" : "\u{1b}[?25l" + return Data(payload.utf8) + } +} + +struct WebRemoteTerminalState: Equatable { + enum Screen: String { + case primary + case alternate + } + + struct Mode: Equatable { + let code: Int + let ansi: Bool + let on: Bool + } + + struct ScrollingRegion: Equatable { + let top: Int + let bottom: Int + let left: Int + let right: Int + + func isValid(columns: Int, rows: Int) -> Bool { + columns > 0 && rows > 0 + && top >= 0 && top <= bottom && bottom < rows + && left >= 0 && left <= right && right < columns + } + } + + struct Cursor: Equatable { + enum Style: String { + case bar + case block + case underline + case blockHollow = "block_hollow" + + func sequence(blinking: Bool) -> String { + let code: Int + switch self { + case .block, .blockHollow: code = blinking ? 1 : 2 + case .underline: code = blinking ? 3 : 4 + case .bar: code = blinking ? 5 : 6 + } + return "\u{1b}[\(code) q" + } + } + + let row: Int + let column: Int + let visible: Bool + let style: Style + let blinking: Bool + } + + let columns: Int + let rows: Int + let activeScreen: Screen + let modes: [Mode] + let scrollingRegion: ScrollingRegion? + let cursor: Cursor +} + +enum WebRemoteProjectPath { + static func resolveExistingDirectory(_ path: String) -> String? { + let expanded = (path as NSString).expandingTildeInPath + let standardized = (expanded as NSString).standardizingPath + var isDirectory: ObjCBool = false + guard standardized.hasPrefix("/"), + FileManager.default.fileExists(atPath: standardized, isDirectory: &isDirectory), + isDirectory.boolValue + else { return nil } + return standardized + } +} + +struct WebRemoteAccessToken { + static func generate() -> String { + var bytes = [UInt8](repeating: 0, count: 32) + if SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) != errSecSuccess { + for index in bytes.indices { + bytes[index] = UInt8.random(in: .min ... .max) + } + } + return bytes.map { String(format: "%02x", $0) }.joined() + } + + static func matches(_ provided: String?, expected: String) -> Bool { + guard let provided else { return false } + return WebRemoteCrypto.constantTimeEquals(Data(provided.utf8), Data(expected.utf8)) + } +} + +/// End-to-end crypto for the web remote. The access token never crosses the +/// wire: the browser proves it knows the token with an HMAC challenge-response, +/// and both sides derive per-direction AES-256-GCM keys from (token, challenge) +/// via HKDF. Everything after the handshake is encrypted, so a passive sniffer +/// on the LAN sees only the random challenge and the proof — not the token, not +/// terminal content, not project paths. +/// +/// Threat model: defends against *passive* sniffing only. Not forward-secret +/// (the session key derives from the long-lived token). Matches the JS client's +/// vendored `crypto.mjs` byte-for-byte: HMAC-SHA256, HKDF (extract+expand), +/// AES-256-GCM with the 16-byte tag appended to the ciphertext. +enum WebRemoteCrypto { + static let nonceLength = 12 // AES-GCM standard nonce + static let tagLength = 16 // AES-GCM authentication tag + static let challengeLength = 32 + static let keyLength = 32 // AES-256 + + /// Domain-separation labels shared with the client. + private static let proofLabel = Data("glint-webremote/v1/proof".utf8) + private static let sessionInfo = Data("glint-webremote/v1/session".utf8) + private static let c2sInfo = Data("c2s".utf8) + private static let s2cInfo = Data("s2c".utf8) + + /// Constant-time equality for secrets (proofs / tokens). Length differences + /// are folded into the accumulator so timing doesn't leak the length. + static func constantTimeEquals(_ lhs: Data, _ rhs: Data) -> Bool { + let left = Array(lhs) + let right = Array(rhs) + var difference = UInt8(left.count == right.count ? 0 : 1) + let count = max(left.count, right.count) + for index in 0 ..< count { + let l = index < left.count ? left[index] : 0 + let r = index < right.count ? right[index] : 0 + difference |= l ^ r + } + return difference == 0 + } + + /// Decode the 64-char lowercase-hex token into its 32 raw bytes. Returns nil + /// for anything that isn't exactly 64 lowercase hex digits. + static func tokenKey(from token: String) -> Data? { + guard token.count == 64, + token.allSatisfy({ $0.isHexDigit && !$0.isUppercase }) + else { return nil } + var bytes = Data() + bytes.reserveCapacity(keyLength) + var index = token.startIndex + while index < token.endIndex { + let next = token.index(index, offsetBy: 2) + guard let byte = UInt8(token[index ..< next], radix: 16) else { return nil } + bytes.append(byte) + index = next + } + return bytes + } + + /// A fresh per-connection random challenge. + static func newChallenge() -> Data { + var bytes = [UInt8](repeating: 0, count: challengeLength) + if SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) != errSecSuccess { + for index in bytes.indices { + bytes[index] = UInt8.random(in: .min ... .max) + } + } + return Data(bytes) + } + + /// Proof of knowledge of `tokenKey` for `challenge`, without revealing the + /// token. `proof = HMAC-SHA256(tokenKey, "…/proof" || challenge)`. + static func proof(tokenKey: Data, challenge: Data) -> Data { + var message = proofLabel + message.append(challenge) + let mac = HMAC.authenticationCode( + for: message, + using: SymmetricKey(data: tokenKey) + ) + return Data(mac) + } + + /// Per-direction AES-256 keys derived from (tokenKey, challenge). + /// `sessionKey = HKDF(tokenKey, salt: challenge, info: session)` then + /// `c2s`/`s2c = HKDF-Expand(sessionKey, info, 32)`. + static func sessionKeys( + tokenKey: Data, + challenge: Data + ) -> (c2s: SymmetricKey, s2c: SymmetricKey) { + let session = HKDF.deriveKey( + inputKeyMaterial: SymmetricKey(data: tokenKey), + salt: challenge, + info: sessionInfo, + outputByteCount: keyLength + ) + let c2s = HKDF.expand( + pseudoRandomKey: session, + info: c2sInfo, + outputByteCount: keyLength + ) + let s2c = HKDF.expand( + pseudoRandomKey: session, + info: s2cInfo, + outputByteCount: keyLength + ) + return (c2s, s2c) + } + + /// 12-byte GCM nonce for a per-direction message counter: 4 zero bytes + /// followed by the 8-byte big-endian counter. Keys are direction-split, so + /// counters only need uniqueness within one direction — never collide. + static func nonce(for counter: UInt64) -> Data { + var nonce = Data(count: nonceLength) + nonce.withUnsafeMutableBytes { buffer in + let bytes = buffer.bindMemory(to: UInt8.self) + var bigEndian = counter.bigEndian + withUnsafeBytes(of: &bigEndian) { counterBytes in + for index in 0 ..< MemoryLayout.size { + bytes[nonceLength - MemoryLayout.size + index] = counterBytes[index] + } + } + } + return nonce + } + + /// Inverse of `nonce(for:)`: read the counter back out of a 12-byte nonce. + /// Used by the receiver's replay guard. + static func counter(fromNonce nonce: Data) -> UInt64? { + guard nonce.count == nonceLength else { return nil } + var value: UInt64 = 0 + for index in 0 ..< MemoryLayout.size { + value <<= 8 + value |= UInt64(nonce[nonceLength - MemoryLayout.size + index]) + } + return value + } + + /// Encrypt `plaintext` into a full frame: `nonce(12) || ciphertext || tag`. + /// The 16-byte tag is appended to the ciphertext, matching the client's + /// vendored noble gcm layout. nil only on a malformed nonce, which the + /// counter encoding can't produce — callers treat nil as fatal. + static func sealFrame(plaintext: Data, key: SymmetricKey, counter: UInt64) -> Data? { + let nonce = self.nonce(for: counter) + guard let gcmNonce = try? AES.GCM.Nonce(data: nonce), + let sealed = try? AES.GCM.seal(plaintext, using: key, nonce: gcmNonce) + else { return nil } + var frame = Data(capacity: nonceLength + sealed.ciphertext.count + tagLength) + frame.append(nonce) + frame.append(sealed.ciphertext) + frame.append(sealed.tag) + return frame + } + + /// Decrypt a frame body (the bytes after the 12-byte nonce): `ciphertext || + /// tag`, authenticated under the supplied nonce. nil on any failure + /// (truncated frame, nonce shape, auth-tag mismatch) — callers drop. + static func openFrame(nonce: Data, body: Data, key: SymmetricKey) -> Data? { + guard nonce.count == nonceLength, + body.count > tagLength, + let gcmNonce = try? AES.GCM.Nonce(data: nonce), + let box = try? AES.GCM.SealedBox( + nonce: gcmNonce, + ciphertext: body.prefix(body.count - tagLength), + tag: body.suffix(tagLength) + ), + let plaintext = try? AES.GCM.open(box, using: key) + else { return nil } + return plaintext + } +} + +enum WebRemoteAccessKeyStore { + private static let defaultsKey = "glint.webRemoteAccessKey" + + static func loadOrCreate(defaults: UserDefaults = .standard) -> String { + if let existing = defaults.string(forKey: defaultsKey), isValid(existing) { + return existing + } + return reset(defaults: defaults) + } + + @discardableResult + static func reset(defaults: UserDefaults = .standard) -> String { + let key = WebRemoteAccessToken.generate() + defaults.set(key, forKey: defaultsKey) + return key + } + + private static func isValid(_ value: String) -> Bool { + value.count == 64 && value.allSatisfy { $0.isHexDigit && !$0.isUppercase } + } +} + +struct WebRemotePorts: Equatable { + let http: UInt16 + var webSocket: UInt16 { http + 1 } +} + +enum WebRemotePortStore { + private static let defaultsKey = "glint.webRemoteHTTPPort" + private static let defaultHTTPPort: UInt16 = 43871 + private static let resetRange: ClosedRange = 49152 ... 65534 + + static func loadOrCreate(defaults: UserDefaults = .standard) -> WebRemotePorts { + let stored = defaults.integer(forKey: defaultsKey) + if let port = UInt16(exactly: stored), port >= 1024, port < UInt16.max { + return WebRemotePorts(http: port) + } + defaults.set(Int(defaultHTTPPort), forKey: defaultsKey) + return WebRemotePorts(http: defaultHTTPPort) + } + + @discardableResult + static func reset( + defaults: UserDefaults = .standard, + randomPort: () -> UInt16 = { UInt16.random(in: resetRange) } + ) -> WebRemotePorts { + let current = loadOrCreate(defaults: defaults).http + let candidate = randomPort() + let next = candidate == current + ? (candidate == resetRange.upperBound ? resetRange.lowerBound : candidate + 1) + : candidate + defaults.set(Int(next), forKey: defaultsKey) + return WebRemotePorts(http: next) + } +} + +enum WebRemoteAccessURL { + static func token(from value: String) -> String? { + guard let fragment = URLComponents(string: value)?.fragment else { return nil } + return URLComponents(string: "?\(fragment)")? + .queryItems? + .first(where: { $0.name == "token" })? + .value + } + + /// The URL with the access-token fragment stripped, so copying or sharing + /// the link never leaks the secret in plaintext. The token must travel + /// out-of-band (the "Access key" field); a browser opening this redacted + /// URL prompts for the key via the auth dialog. + static func redacted(from value: String) -> String { + guard var comps = URLComponents(string: value) else { return value } + comps.fragment = nil + return comps.string ?? value + } +} + +struct WebRemoteHTTPRequest: Equatable { + enum Method: String { + case get = "GET" + case head = "HEAD" + } + + let method: Method + let path: String + + static func parse(_ data: Data) -> WebRemoteHTTPRequest? { + guard let text = String(data: data, encoding: .utf8), + let firstLine = text.components(separatedBy: "\r\n").first + else { return nil } + let parts = firstLine.split(separator: " ", omittingEmptySubsequences: true) + guard parts.count == 3, + let method = Method(rawValue: String(parts[0])), + parts[2].hasPrefix("HTTP/1.") + else { return nil } + let rawPath = String(parts[1]) + let path = rawPath.split(separator: "?", maxSplits: 1, omittingEmptySubsequences: false) + .first.map(String.init) ?? rawPath + guard path.hasPrefix("/"), !path.contains("\0") else { return nil } + return WebRemoteHTTPRequest(method: method, path: path) + } +} + +struct WebRemoteAsset: Equatable { + let resource: String + let fileExtension: String + let contentType: String + let cacheControl: String +} + +enum WebRemoteAssets { + static func asset(for path: String) -> WebRemoteAsset? { + switch path { + case "/", "/index.html": + WebRemoteAsset( + resource: "web-remote-index", + fileExtension: "html", + contentType: "text/html; charset=utf-8", + cacheControl: "no-store" + ) + case "/app.css": + WebRemoteAsset( + resource: "web-remote", + fileExtension: "css", + contentType: "text/css; charset=utf-8", + cacheControl: "no-cache" + ) + case "/app.js": + WebRemoteAsset( + resource: "web-remote", + fileExtension: "js", + contentType: "text/javascript; charset=utf-8", + cacheControl: "no-cache" + ) + case "/xterm.css": + WebRemoteAsset( + resource: "xterm", + fileExtension: "css", + contentType: "text/css; charset=utf-8", + cacheControl: "public, max-age=31536000, immutable" + ) + case "/xterm.mjs": + WebRemoteAsset( + resource: "xterm", + fileExtension: "mjs", + contentType: "text/javascript; charset=utf-8", + cacheControl: "public, max-age=31536000, immutable" + ) + case "/addon-fit.mjs": + WebRemoteAsset( + resource: "addon-fit", + fileExtension: "mjs", + contentType: "text/javascript; charset=utf-8", + cacheControl: "public, max-age=31536000, immutable" + ) + case "/crypto.mjs": + WebRemoteAsset( + resource: "crypto", + fileExtension: "mjs", + contentType: "text/javascript; charset=utf-8", + cacheControl: "public, max-age=31536000, immutable" + ) + case "/symbols-nerd-font-mono.ttf": + WebRemoteAsset( + resource: "SymbolsNerdFontMono-Regular", + fileExtension: "ttf", + contentType: "font/ttf", + cacheControl: "public, max-age=31536000, immutable" + ) + default: + nil + } + } +} + +enum WebRemoteHTTPResponse { + static func make( + status: Int, + reason: String, + contentType: String, + cacheControl: String = "no-store", + body: Data, + includeBody: Bool = true + ) -> Data { + let header = """ + HTTP/1.1 \(status) \(reason)\r + Content-Type: \(contentType)\r + Content-Length: \(body.count)\r + Cache-Control: \(cacheControl)\r + Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'\r + Cross-Origin-Opener-Policy: same-origin\r + Referrer-Policy: no-referrer\r + X-Content-Type-Options: nosniff\r + Connection: close\r + \r + + """ + var response = Data(header.utf8) + if includeBody { + response.append(body) + } + return response + } +} + +/// A non-loopback, up, IPv4 interface this Mac can bind the web remote to. +/// The BSD name (e.g. "en0") is the stable identity; `address` is its current +/// IPv4 and may change across DHCP/sleep — re-resolve at bind time. +struct WebRemoteInterface: Hashable, Identifiable { + var id: String { name } + let name: String + let displayName: String + let address: String +} + +enum WebRemoteAddressResolver { + /// All non-loopback IPv4 addresses, priority-sorted and de-duplicated by + /// address. Used to populate the "All interfaces" access URLs. + static func localIPv4Addresses() -> [String] { + sortedEntries() + .map(\.address) + .reduce(into: [String]()) { result, address in + if !result.contains(address) { result.append(address) } + } + } + + /// One entry per interface (first IPv4 wins), priority-sorted. Drives the + /// "Listen on" picker. + static func interfaces() -> [WebRemoteInterface] { + var seen = Set() + return sortedEntries().compactMap { entry in + guard !seen.contains(entry.interface) else { return nil } + seen.insert(entry.interface) + return WebRemoteInterface( + name: entry.interface, + displayName: entry.interface, + address: entry.address + ) + } + } + + /// Current IPv4 of a named interface, if it is up and non-loopback. + static func currentIPv4(forInterface name: String) -> String? { + interfaces().first { $0.name == name }?.address + } + + /// Raw getifaddrs walk: every (interface, IPv4) pair that is up and not + /// loopback, sorted by the URL/picker display priority. Single source for + /// `localIPv4Addresses()` (dedup by address) and `interfaces()` (dedup by + /// interface name). + private static func sortedEntries() -> [(priority: Int, interface: String, address: String)] { + var head: UnsafeMutablePointer? + guard getifaddrs(&head) == 0, let first = head else { return [] } + defer { freeifaddrs(head) } + + var values: [(priority: Int, interface: String, address: String)] = [] + var pointer: UnsafeMutablePointer? = first + while let current = pointer { + defer { pointer = current.pointee.ifa_next } + guard let addressPointer = current.pointee.ifa_addr, + addressPointer.pointee.sa_family == sa_family_t(AF_INET), + current.pointee.ifa_flags & UInt32(IFF_UP) != 0, + current.pointee.ifa_flags & UInt32(IFF_LOOPBACK) == 0 + else { continue } + + let interface = String(cString: current.pointee.ifa_name) + var address = UnsafeRawPointer(addressPointer) + .assumingMemoryBound(to: sockaddr_in.self).pointee.sin_addr + var buffer = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) + guard inet_ntop(AF_INET, &address, &buffer, socklen_t(INET_ADDRSTRLEN)) != nil else { continue } + values.append((priority(for: interface), interface, String(cString: buffer))) + } + + return values.sorted { + if $0.priority != $1.priority { return $0.priority < $1.priority } + if $0.interface != $1.interface { return $0.interface < $1.interface } + return $0.address < $1.address + } + } + + private static func priority(for interface: String) -> Int { + if interface == "en0" { return 0 } + if interface.hasPrefix("en") { return 1 } + if interface.hasPrefix("utun") { return 2 } + return 3 + } +} + +/// Persisted choice of which local address the web remote binds to. +/// Stored as a string key in UserDefaults: `loopback`, `any`, or an interface +/// name (e.g. `en0`) resolved to its current IPv4 at bind time. +enum WebRemoteListenTarget { + static let loopback = "loopback" + static let any = "any" + + static func requiresActiveAttackWarning(_ key: String) -> Bool { + key != loopback + } + + /// The IPv4 to bind, or `nil` for a wildcard listener (all interfaces). + static func bindAddress(for key: String) -> String? { + switch key { + case Self.loopback: return "127.0.0.1" + case Self.any: return nil + default: return WebRemoteAddressResolver.currentIPv4(forInterface: key) + } + } +} diff --git a/Glint/WebRemote/WebRemoteServer.swift b/Glint/WebRemote/WebRemoteServer.swift new file mode 100644 index 0000000..faf7b04 --- /dev/null +++ b/Glint/WebRemote/WebRemoteServer.swift @@ -0,0 +1,1312 @@ +import CryptoKit +import Foundation +import Network +import os + +private let webRemoteLogger = Logger(subsystem: "app.glint", category: "WebRemote") + +enum WebRemoteStatus: Equatable { + case stopped + case starting + case ready(urls: [String]) + case portConflict(port: UInt16) + case failed(message: String) +} + +struct WebRemoteOutputBuffer { + private struct Boundary { + let sequence: UInt64 + let endOffset: Int + } + + let byteLimit: Int + private(set) var data = Data() + private var boundaries: [Boundary] = [] + + var isEmpty: Bool { data.isEmpty } + + init(byteLimit: Int) { + precondition(byteLimit >= 0) + self.byteLimit = byteLimit + } + + mutating func append(_ chunk: Data, sequence: UInt64) -> Bool { + guard chunk.count <= byteLimit - data.count else { return false } + data.append(chunk) + boundaries.append(Boundary(sequence: sequence, endOffset: data.count)) + return true + } + + mutating func append( + _ bytes: UnsafePointer, + count: Int, + sequence: UInt64 + ) -> Bool { + guard count <= byteLimit - data.count else { return false } + data.append(bytes, count: count) + boundaries.append(Boundary(sequence: sequence, endOffset: data.count)) + return true + } + + mutating func append(contentsOf buffer: WebRemoteOutputBuffer) -> Bool { + guard buffer.data.count <= byteLimit - data.count else { return false } + let baseOffset = data.count + data.append(buffer.data) + boundaries.append(contentsOf: buffer.boundaries.map { + Boundary(sequence: $0.sequence, endOffset: baseOffset + $0.endOffset) + }) + return true + } + + mutating func take(after sequence: UInt64? = nil) -> Data { + let startOffset: Int + if let sequence, + let index = boundaries.firstIndex(where: { $0.sequence > sequence }) { + startOffset = index == boundaries.startIndex ? 0 : boundaries[index - 1].endOffset + } else if sequence == nil { + startOffset = 0 + } else { + startOffset = data.endIndex + } + let result = data.subdata(in: startOffset ..< data.endIndex) + data.removeAll(keepingCapacity: true) + boundaries.removeAll(keepingCapacity: true) + return result + } +} + +struct WebRemoteOutboundBuffer { + enum Item { + case message(Data) + case terminalOutput(pane: String, data: Data) + } + + let maxQueuedOutputBytes: Int + private var items: [Item] = [] + private var queuedOutputBytes = 0 + + init(maxQueuedOutputBytes: Int) { + precondition(maxQueuedOutputBytes >= 0) + self.maxQueuedOutputBytes = maxQueuedOutputBytes + } + + mutating func enqueueMessage(_ data: Data) { + items.append(.message(data)) + } + + mutating func enqueueTerminalOutput(_ data: Data, pane: String) -> Bool { + guard data.count <= maxQueuedOutputBytes - queuedOutputBytes else { return false } + if let last = items.last, + case let .terminalOutput(existingPane, existingData) = last, + existingPane == pane { + var combined = existingData + combined.append(data) + items[items.index(before: items.endIndex)] = .terminalOutput( + pane: pane, + data: combined + ) + } else { + items.append(.terminalOutput(pane: pane, data: data)) + } + queuedOutputBytes += data.count + return true + } + + mutating func next() -> Item? { + guard !items.isEmpty else { return nil } + let item = items.removeFirst() + if case let .terminalOutput(_, data) = item { + queuedOutputBytes -= data.count + } + return item + } + + mutating func removeAll() { + items.removeAll() + queuedOutputBytes = 0 + } +} + +struct WebRemoteAuthThrottle { + private struct Failure { + var count: Int + var lastFailure: TimeInterval + } + + private let countCap: Int + private let expirySeconds: TimeInterval + private var failuresBySource: [String: Failure] = [:] + + init(countCap: Int = 7, expirySeconds: TimeInterval) { + precondition(countCap > 0) + precondition(expirySeconds > 0) + self.countCap = countCap + self.expirySeconds = expirySeconds + } + + mutating func recordFailure( + for source: String, + now: TimeInterval = ProcessInfo.processInfo.systemUptime + ) -> Int { + failuresBySource = failuresBySource.filter { + now >= $0.value.lastFailure + && now - $0.value.lastFailure < expirySeconds + } + let count = min((failuresBySource[source]?.count ?? 0) + 1, countCap) + failuresBySource[source] = Failure(count: count, lastFailure: now) + return count + } + + mutating func removeAll() { + failuresBySource.removeAll() + } +} + +final class WebRemoteServer: @unchecked Sendable { + static let shared = WebRemoteServer() + + static let maxClientConnections = 32 + static let maxUnauthenticatedConnections = 8 + private static let maxIngressOutputBytes = 256 * 1024 + private static let maxSelectionOutputBytes = 512 * 1024 + private static let authFailureCountCap = 7 + private static let authFailureExpirySeconds: TimeInterval = 60 + private static let authenticationTimeoutSeconds: TimeInterval = 30 + + /// Delay before answering a failed `authenticate`, growing exponentially + /// with the source-address failure count to throttle reconnect-based guessing. + /// 0.25s → 0.5s → 1s → … → 16s (capped at the 7th failure). Honest clients + /// almost never fail auth, so this only bites brute-forcers. + static func authBackoffSeconds(forFailures count: Int) -> TimeInterval { + let safe = min(max(count, 1), authFailureCountCap) + let multiplier = pow(2.0, Double(safe - 1)) + return min(0.25 * multiplier, 16) + } + + static func allowsNewClient(total: Int, unauthenticated: Int) -> Bool { + total < maxClientConnections + && unauthenticated < maxUnauthenticatedConnections + } + + static func authSourceKey(for endpoint: NWEndpoint) -> String { + if case let .hostPort(host, _) = endpoint { + return String(describing: host) + } + return String(describing: endpoint) + } + + private enum ListenerKind: Hashable { + case http + case webSocket + } + + private let queue = DispatchQueue(label: "app.glint.web-remote", qos: .utility) + private let subscriptionLock = NSLock() + private var subscribedPanes = Set() + private var pendingTerminalOutput: [String: WebRemoteOutputBuffer] = [:] + private var overflowedTerminalOutput = Set() + private var terminalOutputDrainScheduled = false + private var httpListener: NWListener? + private var webSocketListener: NWListener? + private var clients: [UUID: WebRemoteClientConnection] = [:] + private var authThrottle = WebRemoteAuthThrottle( + countCap: WebRemoteServer.authFailureCountCap, + expirySeconds: WebRemoteServer.authFailureExpirySeconds + ) + private var readyListeners = Set() + private var runID: UUID? + private var token = "" + /// Raw 32-byte key material decoded from `token`; empty until the server + /// starts with a valid token. Drives the challenge-response handshake. + private var tokenKey = Data() + private var ports = WebRemotePortStore.loadOrCreate() + private var listenInterface = WebRemoteListenTarget.loopback + private var lastBoundAddress: String? + private var pathMonitor: NWPathMonitor? + private var pathRestartScheduled = false + private var terminalSizeRevision: UInt64 = 0 + private var assetCache: [String: Data] = [:] + private var statusHandler: ((WebRemoteStatus) -> Void)? + + private init() {} + + func setStatusHandler(_ handler: @escaping (WebRemoteStatus) -> Void) { + queue.async { [weak self] in + self?.statusHandler = handler + } + } + + func start() { + queue.async { [weak self] in + self?.startLocked() + } + } + + func stop() { + queue.async { [weak self] in + self?.stopLocked(emitStatus: true) + } + } + + func resetCredentials() { + queue.async { [weak self] in + self?.resetCredentialsLocked() + } + } + + /// Stage a new bind target. Takes effect on the next `start()`; the store + /// calls `start()` right after when the server is enabled, which + /// stop→start rebinds. + func setListenInterface(_ key: String) { + queue.async { [weak self] in + self?.listenInterface = key + } + } + + func refreshAppearance() { + queue.async { [weak self] in + guard let self else { return } + clients.values + .filter(\.authenticated) + .forEach { self.sendState(to: $0.id) } + } + } + + func forwardTerminalOutput( + pane: String, + bytes: UnsafePointer?, + count: UInt, + sequence: UInt64 + ) { + guard let bytes, count > 0, let byteCount = Int(exactly: count) else { return } + var shouldScheduleDrain = false + subscriptionLock.lock() + let interested = subscribedPanes.contains(pane) + if interested { + if !overflowedTerminalOutput.contains(pane) { + var buffer = pendingTerminalOutput[pane] + ?? WebRemoteOutputBuffer(byteLimit: Self.maxIngressOutputBytes) + if buffer.append(bytes, count: byteCount, sequence: sequence) { + pendingTerminalOutput[pane] = buffer + } else { + pendingTerminalOutput.removeValue(forKey: pane) + overflowedTerminalOutput.insert(pane) + } + } + if !terminalOutputDrainScheduled { + terminalOutputDrainScheduled = true + shouldScheduleDrain = true + } + } + subscriptionLock.unlock() + guard interested else { return } + if shouldScheduleDrain { + queue.async { [weak self] in + self?.drainTerminalOutputLocked() + } + } + } + + private func drainTerminalOutputLocked() { + subscriptionLock.lock() + let batches = pendingTerminalOutput + let overflowedPanes = overflowedTerminalOutput + pendingTerminalOutput.removeAll(keepingCapacity: true) + overflowedTerminalOutput.removeAll(keepingCapacity: true) + terminalOutputDrainScheduled = false + subscriptionLock.unlock() + + let overflowedClientIDs = clients.values.compactMap { client -> UUID? in + guard client.authenticated, + let pane = client.pendingPane ?? client.subscribedPane, + overflowedPanes.contains(pane) + else { return nil } + return client.id + } + overflowedClientIDs.forEach { dropSlowClientLocked($0) } + + var slowClientIDs = Set() + for (pane, buffer) in batches { + for client in clients.values where client.authenticated { + if client.pendingPane == pane { + if !client.pendingSelectionOutput.append(contentsOf: buffer) { + slowClientIDs.insert(client.id) + } + } else if client.subscribedPane == pane, + !client.sendTerminalOutput(buffer.data, pane: pane) { + slowClientIDs.insert(client.id) + } + } + } + slowClientIDs.forEach { dropSlowClientLocked($0) } + } + + private func startLocked() { + stopLocked(emitStatus: false) + emit(.starting) + token = WebRemoteAccessKeyStore.loadOrCreate() + tokenKey = WebRemoteCrypto.tokenKey(from: token) ?? Data() + ports = WebRemotePortStore.loadOrCreate() + let currentRun = UUID() + runID = currentRun + + do { + let isExplicitWildcard = listenInterface == WebRemoteListenTarget.any + let bindAddress = WebRemoteListenTarget.bindAddress(for: listenInterface) + // A user-chosen NIC that currently has no IPv4 must fail loudly. + // bindAddress returns nil both for "explicit All interfaces" and + // for "selected NIC vanished"; conflating them would silently widen + // exposure to 0.0.0.0 — the opposite of picking a NIC. + guard isExplicitWildcard || bindAddress != nil else { + failLocked("Selected interface is no longer available.") + return + } + lastBoundAddress = bindAddress + // Restrict the listener to the chosen local address instead of the + // default wildcard (0.0.0.0 + ::). loopback → 127.0.0.1, a named + // interface → its current IPv4. The endpoint's port must be 0 + // ("any"); a concrete port collides with the listener's `on:` and + // fails with EINVAL (NWError 22). + let localEndpoint: NWEndpoint? = bindAddress.flatMap { address in + IPv4Address(address).map { .hostPort(host: .ipv4($0), port: 0) } + } + + let httpParameters = NWParameters.tcp + httpParameters.allowLocalEndpointReuse = true + guard let httpPort = NWEndpoint.Port(rawValue: ports.http), + let webSocketPort = NWEndpoint.Port(rawValue: ports.webSocket) + else { + failLocked("Invalid web remote port.") + return + } + httpParameters.requiredLocalEndpoint = localEndpoint + + let http = try NWListener(using: httpParameters, on: httpPort) + + let webSocketParameters = NWParameters.tcp + webSocketParameters.allowLocalEndpointReuse = true + webSocketParameters.requiredLocalEndpoint = localEndpoint + let options = NWProtocolWebSocket.Options() + options.autoReplyPing = true + webSocketParameters.defaultProtocolStack.applicationProtocols.insert(options, at: 0) + let webSocket = try NWListener(using: webSocketParameters, on: webSocketPort) + + httpListener = http + webSocketListener = webSocket + configure(http, kind: .http, runID: currentRun) + configure(webSocket, kind: .webSocket, runID: currentRun) + http.newConnectionHandler = { [weak self] connection in + self?.handleHTTPConnection(connection) + } + webSocket.newConnectionHandler = { [weak self] connection in + self?.handleWebSocketConnection(connection) + } + http.start(queue: queue) + webSocket.start(queue: queue) + startPathMonitorLocked(currentRun) + } catch { + failLocked(error.localizedDescription) + } + } + + /// Watch for the bound NIC's IPv4 changing while the server runs (DHCP + /// renewal, Wi-Fi switch, sleep/wake). loopback and explicit-wildcard are + /// address-stable / address-agnostic and skip the monitor. On a real + /// change we coalesce into a single rebind (stop+start re-resolves the + /// current IPv4 and refreshes the access URLs); a vanished NIC rebinds and + /// then fails loudly via the guard in `startLocked`. + private func startPathMonitorLocked(_ runID: UUID) { + pathMonitor?.cancel() + guard listenInterface != WebRemoteListenTarget.loopback, + listenInterface != WebRemoteListenTarget.any, + lastBoundAddress != nil + else { + pathMonitor = nil + return + } + let monitor = NWPathMonitor() + monitor.pathUpdateHandler = { [weak self] _ in + self?.handlePathUpdate(runID: runID) + } + monitor.start(queue: queue) + pathMonitor = monitor + } + + private func handlePathUpdate(runID: UUID) { + guard runID == self.runID, !pathRestartScheduled else { return } + let current = WebRemoteAddressResolver.currentIPv4(forInterface: listenInterface) + guard current != lastBoundAddress else { return } + // NWPathMonitor can fire a burst of updates on a topology change; wait + // for things to settle, then rebind once. + pathRestartScheduled = true + queue.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self, self.pathRestartScheduled else { return } + self.pathRestartScheduled = false + guard self.runID == runID else { return } + webRemoteLogger.info("Web remote interface address changed; rebinding") + self.startLocked() + } + } + + private func configure(_ listener: NWListener, kind: ListenerKind, runID: UUID) { + listener.stateUpdateHandler = { [weak self] state in + guard let self, self.runID == runID else { return } + switch state { + case .ready: + self.readyListeners.insert(kind) + if self.readyListeners.count == 2 { + webRemoteLogger.info("Web remote listening on ports \(self.ports.http) and \(self.ports.webSocket)") + self.emit(.ready(urls: self.accessURLsLocked())) + } + case let .failed(error): + let port = kind == .http ? self.ports.http : self.ports.webSocket + if case .posix(.EADDRINUSE) = error { + self.failLocked(status: .portConflict(port: port)) + } else { + self.failLocked(error.localizedDescription) + } + default: + break + } + } + } + + private func resetCredentialsLocked() { + WebRemoteAccessKeyStore.reset() + WebRemotePortStore.reset() + startLocked() + } + + private func accessURLsLocked() -> [String] { + let hosts: [String] + switch listenInterface { + case WebRemoteListenTarget.loopback: + hosts = ["127.0.0.1"] + case WebRemoteListenTarget.any: + let addresses = WebRemoteAddressResolver.localIPv4Addresses() + hosts = addresses.isEmpty ? ["127.0.0.1"] : addresses + default: + // Named interface: show its current IPv4. If the interface has gone + // away, fall back to loopback for display — the bind itself will + // fail and surface `.failed`. + let address = WebRemoteAddressResolver.currentIPv4(forInterface: listenInterface) + hosts = [address ?? "127.0.0.1"] + } + return hosts.map { "http://\($0):\(ports.http)/#token=\(token)" } + } + + private func stopLocked(emitStatus: Bool) { + let controlledPanes = controlledPanesLocked() + runID = nil + httpListener?.cancel() + webSocketListener?.cancel() + httpListener = nil + webSocketListener = nil + clients.values.forEach { $0.cancel() } + clients.removeAll() + authThrottle.removeAll() + readyListeners.removeAll() + token = "" + tokenKey = Data() + pathMonitor?.cancel() + pathMonitor = nil + pathRestartScheduled = false + lastBoundAddress = nil + updateSubscribedPanesLocked() + releaseTerminalSizes(controlledPanes) + if emitStatus { + webRemoteLogger.info("Web remote stopped") + emit(.stopped) + } + } + + private func failLocked(_ message: String) { + failLocked(status: .failed(message: message)) + } + + private func failLocked(status: WebRemoteStatus) { + let controlledPanes = controlledPanesLocked() + runID = nil + httpListener?.cancel() + webSocketListener?.cancel() + httpListener = nil + webSocketListener = nil + clients.values.forEach { $0.cancel() } + clients.removeAll() + authThrottle.removeAll() + readyListeners.removeAll() + token = "" + tokenKey = Data() + pathMonitor?.cancel() + pathMonitor = nil + pathRestartScheduled = false + lastBoundAddress = nil + updateSubscribedPanesLocked() + releaseTerminalSizes(controlledPanes) + webRemoteLogger.error("Web remote failed: \(String(describing: status), privacy: .public)") + emit(status) + } + + private func emit(_ status: WebRemoteStatus) { + guard let statusHandler else { return } + DispatchQueue.main.async { + statusHandler(status) + } + } + + private func handleHTTPConnection(_ connection: NWConnection) { + connection.stateUpdateHandler = { [weak self, weak connection] state in + guard let self, let connection else { return } + switch state { + case .ready: + self.receiveHTTPRequest(connection, buffer: Data()) + case .failed, + .cancelled: + connection.stateUpdateHandler = nil + default: + break + } + } + connection.start(queue: queue) + } + + private func receiveHTTPRequest(_ connection: NWConnection, buffer: Data) { + connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { + [weak self, weak connection] content, _, complete, error in + guard let self, let connection else { return } + if error != nil { + connection.cancel() + return + } + var next = buffer + if let content { next.append(content) } + if next.count > 16_384 { + self.sendHTTPError(431, reason: "Request Header Fields Too Large", to: connection) + return + } + if next.range(of: Data([13, 10, 13, 10])) != nil { + self.serveHTTPRequest(next, on: connection) + return + } + if complete { + self.sendHTTPError(400, reason: "Bad Request", to: connection) + return + } + self.receiveHTTPRequest(connection, buffer: next) + } + } + + private func serveHTTPRequest(_ data: Data, on connection: NWConnection) { + guard let request = WebRemoteHTTPRequest.parse(data) else { + sendHTTPError(400, reason: "Bad Request", to: connection) + return + } + guard let asset = WebRemoteAssets.asset(for: request.path) else { + sendHTTPError(404, reason: "Not Found", to: connection) + return + } + let cacheKey = "\(asset.resource).\(asset.fileExtension)" + let body: Data + if let cached = assetCache[cacheKey] { + body = cached + } else { + guard let url = Bundle.main.url( + forResource: asset.resource, + withExtension: asset.fileExtension + ), let loaded = try? Data(contentsOf: url) else { + sendHTTPError(500, reason: "Internal Server Error", to: connection) + return + } + assetCache[cacheKey] = loaded + body = loaded + } + + let response = WebRemoteHTTPResponse.make( + status: 200, + reason: "OK", + contentType: asset.contentType, + cacheControl: asset.cacheControl, + body: body, + includeBody: request.method == .get + ) + sendHTTP(response, to: connection) + } + + private func sendHTTPError(_ status: Int, reason: String, to connection: NWConnection) { + let body = Data("\(status) \(reason)\n".utf8) + let response = WebRemoteHTTPResponse.make( + status: status, + reason: reason, + contentType: "text/plain; charset=utf-8", + body: body + ) + sendHTTP(response, to: connection) + } + + private func sendHTTP(_ data: Data, to connection: NWConnection) { + connection.send(content: data, completion: .contentProcessed { _ in + connection.cancel() + }) + } + + private func handleWebSocketConnection(_ connection: NWConnection) { + let unauthenticated = clients.values.lazy.filter { !$0.authenticated }.count + guard Self.allowsNewClient( + total: clients.count, + unauthenticated: unauthenticated + ) else { + webRemoteLogger.warning("Rejecting WebSocket client: connection limit reached") + connection.cancel() + return + } + let id = UUID() + let client = WebRemoteClientConnection( + id: id, + authSourceKey: Self.authSourceKey(for: connection.endpoint), + connection: connection, + server: self + ) + clients[id] = client + client.start( + on: queue, + authenticationTimeout: Self.authenticationTimeoutSeconds + ) + } + + fileprivate func removeClient(_ id: UUID) { + queue.async { [weak self] in + self?.removeClientLocked(id, cancelConnection: false) + } + } + + private func dropSlowClientLocked(_ id: UUID) { + dropClientLocked(id, reason: "send queue overflow; will reconnect and resnapshot") + } + + private func removeClientLocked(_ id: UUID, cancelConnection: Bool) { + guard let client = clients.removeValue(forKey: id) else { return } + let affectedPanes = Set([client.subscribedPane, client.pendingPane].compactMap { $0 }) + if cancelConnection { client.cancel() } + updateSubscribedPanesLocked() + affectedPanes.forEach { reconcileTerminalSizeLocked(for: $0) } + } + + /// Entry point from the receive loop. Pre-encryption, the only plaintext a + /// client may send is the `authenticate` proof — pass it straight through. + /// Once encrypted, every inbound frame is `nonce(12) || ciphertext || tag` + /// and is decrypted here before JSON parsing. A truncated frame, a replayed + /// (regressed) nonce, or an auth-tag mismatch drops the connection. + fileprivate func handleWebSocketFrame(_ content: Data, from clientID: UUID) { + guard let client = clients[clientID] else { return } + guard content.count <= 1_048_576 else { + dropClientLocked(clientID, reason: "oversized frame") + return + } + guard client.encrypted, + let c2sKey = client.c2sKey, + content.count > WebRemoteCrypto.nonceLength + WebRemoteCrypto.tagLength + else { + handleWebSocketData(content, from: clientID) + return + } + let nonce = content.prefix(WebRemoteCrypto.nonceLength) + let body = content.subdata(in: WebRemoteCrypto.nonceLength ..< content.count) + guard let counter = WebRemoteCrypto.counter(fromNonce: nonce), + counter >= client.receiveCounter, + let plaintext = WebRemoteCrypto.openFrame(nonce: nonce, body: body, key: c2sKey) + else { + dropClientLocked(clientID, reason: "decrypt/replay failure") + return + } + client.receiveCounter = counter + 1 + handleWebSocketData(plaintext, from: clientID) + } + + fileprivate func handleWebSocketData(_ data: Data, from clientID: UUID) { + guard data.count <= 1_048_576, + let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let type = object["type"] as? String, + let client = clients[clientID] + else { + sendError("bad-request", to: clientID) + return + } + + if type == "authenticate" { + // Challenge-response: the client proves it knows the token with an + // HMAC over the per-connection challenge, without ever sending the + // token. A previous failure is still inside its backoff window; + // ignore the new attempt — `completeAuthFailure` answers it. This + // caps a flooding attacker at one pending timer per client. + guard !client.authBackoffPending else { return } + guard let proofString = object["proof"] as? String, + let provided = Data(base64Encoded: proofString), + let challenge = client.pendingChallenge, + !tokenKey.isEmpty + else { + failAuthenticationLocked(for: clientID) + return + } + let expected = WebRemoteCrypto.proof(tokenKey: tokenKey, challenge: challenge) + guard WebRemoteCrypto.constantTimeEquals(provided, expected) else { + failAuthenticationLocked(for: clientID) + return + } + let keys = WebRemoteCrypto.sessionKeys(tokenKey: tokenKey, challenge: challenge) + client.beginEncryption(c2s: keys.c2s, s2c: keys.s2c) + client.pendingChallenge = nil + client.authenticated = true + // The first frame sent after here is encrypted; the client learns + // auth succeeded by successfully decrypting it. + sendJSON(["type": "authenticated"], to: clientID) + sendState(to: clientID) + return + } + + guard client.authenticated else { + sendError("unauthorized", to: clientID) + return + } + + switch type { + case "list": + sendState(to: clientID) + case "select": + guard let pane = object["pane"] as? String, + pane.count <= 128, + let size = WebRemoteTerminalSize.parse(object) + else { + sendError("bad-request", to: clientID) + return + } + selectPane(pane, size: size, for: clientID) + case "resize": + guard let pane = object["pane"] as? String, + pane == client.subscribedPane, + let size = WebRemoteTerminalSize.parse(object) + else { + sendError("bad-request", to: clientID) + return + } + resizePane(pane, size: size, for: clientID) + case "input": + guard let pane = object["pane"] as? String, + pane == client.subscribedPane, + let encoded = object["data"] as? String, + let bytes = Data(base64Encoded: encoded), + !bytes.isEmpty, + bytes.count <= 65_536 + else { + sendError("bad-request", to: clientID) + return + } + sendInput(bytes, pane: pane, clientID: clientID) + case "createProject": + guard let path = object["path"] as? String, !path.isEmpty, path.count <= 4096 else { + sendError("bad-request", to: clientID) + return + } + createProject(path: path, clientID: clientID) + case "createTerminal": + guard let value = object["workspace"] as? String, + value.count <= 36, + let workspaceID = UUID(uuidString: value) + else { + sendError("bad-request", to: clientID) + return + } + createTerminal(workspace: workspaceID, clientID: clientID) + case "closeTerminal": + guard let pane = object["pane"] as? String, pane.count <= 128 else { + sendError("bad-request", to: clientID) + return + } + closeTerminal( + pane: pane, + confirmed: object["confirmed"] as? Bool ?? false, + clientID: clientID + ) + default: + sendError("unknown-command", to: clientID) + } + } + + private func sendState(to clientID: UUID) { + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + var message: [String: Any] = [ + "type": "state", + "workspaces": store.webRemoteWorkspacePayload(), + "theme": store.webRemoteThemePayload(), + ] + if let brand = store.webRemoteBrandPayload() { + message["brand"] = brand + } + if let selected = store.selectedWorkspaceID { + message["selectedWorkspace"] = selected.uuidString + } + self.queue.async { [weak self] in + self?.sendJSON(message, to: clientID) + } + } + } + + private func selectPane( + _ pane: String, + size: WebRemoteTerminalSize, + for clientID: UUID + ) { + guard let client = clients[clientID], client.pendingPane == nil else { + sendError("selection-in-progress", to: clientID) + return + } + + let previousPane = client.subscribedPane + client.subscribedPane = nil + client.pendingPane = pane + client.pendingSelectionOutput = WebRemoteOutputBuffer( + byteLimit: Self.maxSelectionOutputBytes + ) + client.terminalSize = nil + updateSubscribedPanesLocked() + if let previousPane { + reconcileTerminalSizeLocked(for: previousPane) + } + + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + if let error = store.controlFocus(pane: pane, activateApp: false) { + self.queue.async { [weak self] in + self?.finishSelectionFailure(error, pane: pane, clientID: clientID) + } + return + } + let result = store.webRemoteTerminalSnapshot(pane: pane) + self.queue.async { [weak self, weak store] in + guard let self, + let store, + let client = clients[clientID], + client.authenticated, + client.pendingPane == pane + else { return } + switch result { + case let .success(snapshot): + let bufferedOutput = client.pendingSelectionOutput.take( + after: snapshot.outputSequence + ) + client.pendingPane = nil + client.subscribedPane = pane + recordTerminalSizeLocked(size, for: client) + updateSubscribedPanesLocked() + sendJSON([ + "type": "snapshot", + "pane": pane, + "data": snapshot.payload.base64EncodedString(), + ], to: clientID) + if !bufferedOutput.isEmpty, + !client.sendTerminalOutput(bufferedOutput, pane: pane) { + dropSlowClientLocked(clientID) + return + } + DispatchQueue.main.async { [weak self, weak store] in + guard let self, let store else { return } + if let error = store.webRemoteSetTerminalSize(pane: pane, size: size) { + self.queue.async { [weak self] in self?.sendError(error, to: clientID) } + } + } + case let .failure(error): + finishSelectionFailure(error, pane: pane, clientID: clientID) + } + } + } + } + + private func finishSelectionFailure(_ error: String, pane: String, clientID: UUID) { + guard let client = clients[clientID], client.pendingPane == pane else { return } + _ = client.pendingSelectionOutput.take() + client.pendingPane = nil + updateSubscribedPanesLocked() + reconcileTerminalSizeLocked(for: pane) + sendError(error, to: clientID) + } + + private func resizePane( + _ pane: String, + size: WebRemoteTerminalSize, + for clientID: UUID + ) { + guard let client = clients[clientID] else { return } + recordTerminalSizeLocked(size, for: client) + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + if let error = store.webRemoteSetTerminalSize(pane: pane, size: size) { + self.queue.async { [weak self] in self?.sendError(error, to: clientID) } + } + } + } + + private func sendInput(_ data: Data, pane: String, clientID: UUID) { + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + if let error = store.webRemoteSendInput(pane: pane, data: data) { + self.queue.async { [weak self] in self?.sendError(error, to: clientID) } + } + } + } + + private func createProject(path: String, clientID: UUID) { + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + switch store.webRemoteOpenProject(path: path) { + case let .success(workspaceID): + self.queue.async { [weak self] in + self?.sendJSON([ + "type": "projectCreated", + "workspace": workspaceID.uuidString, + ], to: clientID) + self?.sendState(to: clientID) + } + case let .failure(error): + self.queue.async { [weak self] in self?.sendError(error, to: clientID) } + } + } + } + + private func createTerminal(workspace workspaceID: UUID, clientID: UUID) { + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + switch store.webRemoteCreateTerminal(workspace: workspaceID) { + case let .success(pane): + self.queue.async { [weak self] in + self?.sendJSON([ + "type": "terminalCreated", + "pane": pane, + ], to: clientID) + self?.sendState(to: clientID) + } + case let .failure(error): + self.queue.async { [weak self] in self?.sendError(error, to: clientID) } + } + } + } + + private func closeTerminal(pane: String, confirmed: Bool, clientID: UUID) { + DispatchQueue.main.async { [weak self] in + guard let self, let store = WorkspaceStore.current else { return } + switch store.webRemoteCloseTerminal(pane: pane, confirmed: confirmed) { + case .success: + self.queue.async { [weak self] in + self?.finishTerminalCloseLocked(pane: pane) + } + case .confirmationRequired: + self.queue.async { [weak self] in + self?.sendJSON([ + "type": "terminalCloseConfirmation", + "pane": pane, + ], to: clientID) + } + case let .failure(error): + self.queue.async { [weak self] in self?.sendError(error, to: clientID) } + } + } + } + + private func finishTerminalCloseLocked(pane: String) { + let authenticatedClients = clients.values.filter(\.authenticated) + for client in authenticatedClients { + if client.subscribedPane == pane { + client.subscribedPane = nil + client.terminalSize = nil + } + if client.pendingPane == pane { + client.pendingPane = nil + _ = client.pendingSelectionOutput.take() + } + } + updateSubscribedPanesLocked() + for client in authenticatedClients { + sendJSON([ + "type": "terminalClosed", + "pane": pane, + ], to: client.id) + sendState(to: client.id) + } + } + + private func sendJSON(_ object: [String: Any], to clientID: UUID) { + guard let data = SafeJSON.data(object) else { return } + clients[clientID]?.send(data) + } + + private func sendError(_ code: String, to clientID: UUID) { + sendJSON([ + "type": "error", + "code": code, + ], to: clientID) + } + + private func completeAuthFailure(for clientID: UUID) { + guard let client = clients[clientID] else { return } + client.authBackoffPending = false + // Connection is still pre-encryption during the handshake, so the + // error travels as a plaintext frame. + sendError("unauthorized", to: clientID) + } + + /// A failed/missing proof schedules an exponentially-backed-off + /// `unauthorized`. Failure history is shared by source address, so reconnecting + /// does not reset the delay. + private func failAuthenticationLocked(for clientID: UUID) { + guard let client = clients[clientID] else { return } + client.authBackoffPending = true + let failureCount = authThrottle.recordFailure(for: client.authSourceKey) + let delay = Self.authBackoffSeconds(forFailures: failureCount) + queue.asyncAfter(deadline: .now() + delay) { [weak self] in + self?.completeAuthFailure(for: clientID) + } + } + + private func dropClientLocked(_ id: UUID, reason: String) { + webRemoteLogger.warning("Dropping WebSocket client: \(reason, privacy: .public)") + removeClientLocked(id, cancelConnection: true) + } + + private func updateSubscribedPanesLocked() { + let panes = Set(clients.values.flatMap { client -> [String] in + guard client.authenticated else { return [] } + return [client.subscribedPane, client.pendingPane].compactMap { $0 } + }) + subscriptionLock.lock() + subscribedPanes = panes + pendingTerminalOutput = pendingTerminalOutput.filter { panes.contains($0.key) } + overflowedTerminalOutput.formIntersection(panes) + subscriptionLock.unlock() + } + + private func controlledPanesLocked() -> Set { + Set(clients.values.flatMap { client in + [client.subscribedPane, client.pendingPane].compactMap { $0 } + }) + } + + private func recordTerminalSizeLocked( + _ size: WebRemoteTerminalSize, + for client: WebRemoteClientConnection + ) { + terminalSizeRevision &+= 1 + client.terminalSize = size + client.terminalSizeRevision = terminalSizeRevision + } + + private func reconcileTerminalSizeLocked(for pane: String) { + let size = clients.values + .filter { $0.authenticated && $0.subscribedPane == pane && $0.terminalSize != nil } + .max { $0.terminalSizeRevision < $1.terminalSizeRevision }? + .terminalSize + DispatchQueue.main.async { + guard let store = WorkspaceStore.current else { return } + if let size { + _ = store.webRemoteSetTerminalSize(pane: pane, size: size) + } else { + store.webRemoteReleaseTerminalSize(pane: pane) + } + } + } + + private func releaseTerminalSizes(_ panes: Set) { + guard !panes.isEmpty else { return } + DispatchQueue.main.async { + guard let store = WorkspaceStore.current else { return } + panes.forEach { store.webRemoteReleaseTerminalSize(pane: $0) } + } + } +} + +private final class WebRemoteClientConnection: @unchecked Sendable { + let id: UUID + let authSourceKey: String + var authenticated = false + var subscribedPane: String? + var pendingPane: String? + var pendingSelectionOutput = WebRemoteOutputBuffer(byteLimit: 0) + var terminalSize: WebRemoteTerminalSize? + var terminalSizeRevision: UInt64 = 0 + var authBackoffPending = false + + /// Challenge-response + encryption state. `pendingChallenge` is set when the + /// connection comes up; once the proof verifies, `encrypted` flips true and + /// `c2s`/`s2c` carry the per-direction AES-256-GCM keys. Counters are + /// per-direction and never reused under their key. + var pendingChallenge: Data? + var encrypted = false + var c2sKey: SymmetricKey? + var s2cKey: SymmetricKey? + var sendCounter: UInt64 = 0 + var receiveCounter: UInt64 = 0 + + private static let maxQueuedOutputBytes = 512 * 1024 + private let connection: NWConnection + private weak var server: WebRemoteServer? + private var outbound = WebRemoteOutboundBuffer( + maxQueuedOutputBytes: WebRemoteClientConnection.maxQueuedOutputBytes + ) + private var sendInFlight = false + private var cancelled = false + + init( + id: UUID, + authSourceKey: String, + connection: NWConnection, + server: WebRemoteServer + ) { + self.id = id + self.authSourceKey = authSourceKey + self.connection = connection + self.server = server + } + + func start(on queue: DispatchQueue, authenticationTimeout: TimeInterval) { + connection.stateUpdateHandler = { [weak self] state in + guard let self else { return } + switch state { + case .ready: + // Issue the per-connection challenge before reading anything. + // Sent as a plaintext text frame (we are still pre-encryption). + pendingChallenge = WebRemoteCrypto.newChallenge() + if let challenge = pendingChallenge, + let payload = SafeJSON.data([ + "type": "auth-challenge", + "challenge": challenge.base64EncodedString(), + ]) { + send(payload) + } + receiveNextMessage() + case .failed, + .cancelled: + server?.removeClient(id) + default: + break + } + } + connection.start(queue: queue) + queue.asyncAfter(deadline: .now() + authenticationTimeout) { [weak self] in + guard let self, !authenticated else { return } + cancel() + } + } + + func cancel() { + guard !cancelled else { return } + cancelled = true + outbound.removeAll() + _ = pendingSelectionOutput.take() + connection.cancel() + } + + /// Activate encryption after the proof verifies. Counters start at 0 for + /// both directions; the first encrypted frame is `authenticated`. + func beginEncryption(c2s: SymmetricKey, s2c: SymmetricKey) { + c2sKey = c2s + s2cKey = s2c + sendCounter = 0 + receiveCounter = 0 + encrypted = true + } + + func send(_ data: Data) { + guard !cancelled else { return } + outbound.enqueueMessage(data) + sendNextIfNeeded() + } + + func sendTerminalOutput(_ data: Data, pane: String) -> Bool { + guard !cancelled, + outbound.enqueueTerminalOutput(data, pane: pane) + else { return false } + sendNextIfNeeded() + return true + } + + private func sendNextIfNeeded() { + guard !cancelled, !sendInFlight, let item = outbound.next() else { return } + let data: Data? + switch item { + case let .message(message): + data = message + case let .terminalOutput(pane, output): + data = SafeJSON.data([ + "type": "output", + "pane": pane, + "data": output.base64EncodedString(), + ]) + } + guard let data else { + sendNextIfNeeded() + return + } + + // Once encrypted, wrap the JSON plaintext as a binary GCM frame and + // send it as a binary WS message. The counter is consumed only here, at + // actual send time — items are never re-sent (a failed send cancels the + // connection), so nonces are never reused. + var wireData = data + var opcode = NWProtocolWebSocket.Opcode.text + if encrypted, let s2c = s2cKey { + guard let frame = WebRemoteCrypto.sealFrame( + plaintext: data, + key: s2c, + counter: sendCounter + ) else { + cancel() + return + } + sendCounter &+= 1 + wireData = frame + opcode = .binary + } + + let metadata = NWProtocolWebSocket.Metadata(opcode: opcode) + let context = NWConnection.ContentContext( + identifier: "glint-web-remote", + metadata: [metadata] + ) + sendInFlight = true + connection.send( + content: wireData, + contentContext: context, + isComplete: true, + completion: .contentProcessed { [weak self] error in + guard let self else { return } + self.sendInFlight = false + if let error { + webRemoteLogger.error("WebSocket send failed: \(error.localizedDescription, privacy: .public)") + self.cancel() + } else { + self.sendNextIfNeeded() + } + } + ) + } + + private func receiveNextMessage() { + connection.receiveMessage { [weak self] content, context, _, error in + guard let self else { return } + if error != nil { + server?.removeClient(id) + return + } + guard let content, !content.isEmpty else { + receiveNextMessage() + return + } + let metadata = context?.protocolMetadata(definition: NWProtocolWebSocket.definition) + as? NWProtocolWebSocket.Metadata + guard let metadata, metadata.opcode == .text || metadata.opcode == .binary else { + receiveNextMessage() + return + } + server?.handleWebSocketFrame(content, from: id) + receiveNextMessage() + } + } +} diff --git a/Glint/Workspace/WorkspaceStore.swift b/Glint/Workspace/WorkspaceStore.swift index 21df1b6..92605bd 100644 --- a/Glint/Workspace/WorkspaceStore.swift +++ b/Glint/Workspace/WorkspaceStore.swift @@ -938,6 +938,7 @@ final class WorkspaceStore: ObservableObject { didSet { UserDefaults.standard.set(accentName, forKey: "glint.accentName") GhosttyManager.shared.reloadConfig() + WebRemoteServer.shared.refreshAppearance() } } @@ -952,6 +953,7 @@ final class WorkspaceStore: ObservableObject { GhosttyManager.shared.reloadConfig() GhosttyManager.shared.syncWindowAppearance() // 浅/暗主题 → 玻璃材质跟随 themeRevision &+= 1 + WebRemoteServer.shared.refreshAppearance() } } @@ -968,6 +970,7 @@ final class WorkspaceStore: ObservableObject { GhosttyManager.shared.reloadConfig() GhosttyManager.shared.syncWindowAppearance() themeRevision &+= 1 + WebRemoteServer.shared.refreshAppearance() } // MARK: 透明度与模糊 @@ -1022,6 +1025,7 @@ final class WorkspaceStore: ObservableObject { didSet { UserDefaults.standard.set(appIconPreset.rawValue, forKey: "glint.appIconPreset") applyAppIcon() + WebRemoteServer.shared.refreshAppearance() } } @@ -1120,6 +1124,55 @@ final class WorkspaceStore: ObservableObject { } } + @Published var webRemoteEnabled: Bool = (UserDefaults.standard.object(forKey: "glint.webRemoteEnabled") as? Bool) ?? false { + didSet { + UserDefaults.standard.set(webRemoteEnabled, forKey: "glint.webRemoteEnabled") + if webRemoteEnabled { WebRemoteServer.shared.start() } + else { WebRemoteServer.shared.stop() } + } + } + + /// Which local address the web remote binds: `loopback`, `any`, or an + /// interface name (e.g. `en0`). Defaults to loopback — the safest choice; + /// users who want LAN access pick a NIC or "All interfaces" explicitly. + @Published var webRemoteListenInterface: String = { + let key = "glint.webRemoteListenInterface" + let stored = UserDefaults.standard.string(forKey: key) + return stored?.isEmpty == false ? stored! : WebRemoteListenTarget.loopback + }() { + didSet { + guard oldValue != webRemoteListenInterface else { return } + UserDefaults.standard.set(webRemoteListenInterface, forKey: "glint.webRemoteListenInterface") + WebRemoteServer.shared.setListenInterface(webRemoteListenInterface) + if webRemoteEnabled { WebRemoteServer.shared.start() } + } + } + + /// Bind targets currently available on this Mac, for the "Listen on" menu. + /// Snapshotted at init; call `refreshWebRemoteInterfaces()` to rescan after + /// networks change (e.g. joining a different Wi-Fi). + @Published private(set) var webRemoteInterfaceOptions: [WebRemoteInterface] = WebRemoteAddressResolver.interfaces() + + func refreshWebRemoteInterfaces() { + webRemoteInterfaceOptions = WebRemoteAddressResolver.interfaces() + } + + @Published private(set) var webRemoteStatus: WebRemoteStatus = .stopped + @Published private var webRemoteControlledPanes = Set() + + var webRemoteAccessURLs: [String] { + guard case let .ready(urls) = webRemoteStatus else { return [] } + return urls + } + + var webRemoteAccessKey: String? { + webRemoteAccessURLs.first.flatMap(WebRemoteAccessURL.token(from:)) + } + + func resetWebRemoteCredentials() { + WebRemoteServer.shared.resetCredentials() + } + /// Whether the sidebar's "Archived" section is currently expanded. /// Persists across launches so a user who keeps it open doesn't have to /// re-open it every cold start. @@ -1707,6 +1760,12 @@ final class WorkspaceStore: ObservableObject { // docs/external-pane-control.md. Toggling it later is live. if externalControlEnabled { ControlBridge.shared.start() } else { ControlBridge.shared.reapStale() } + WebRemoteServer.shared.setStatusHandler { [weak self] status in + self?.webRemoteStatus = status + } + WebRemoteServer.shared.setListenInterface(webRemoteListenInterface) + if webRemoteEnabled { WebRemoteServer.shared.start() } + else { WebRemoteServer.shared.stop() } Self.autoInstallAgentHooksOnFirstLaunch(socketPath: AgentBridge.shared.socketPath) self.claudeHooksInstalled = AgentHookInstaller.isInstalled() self.codexHooksInstalled = CodexHookInstaller.isInstalled() @@ -1770,6 +1829,7 @@ final class WorkspaceStore: ObservableObject { guard freeIdleTerminalsEnabled else { return } let timeout = TimeInterval(idleTerminalTimeoutSeconds) for (key, view) in surfaceViews { + guard !webRemoteControlledPanes.contains(key) else { continue } view.takeOfflineIfEligible( enabled: true, timeout: timeout, @@ -2708,7 +2768,7 @@ final class WorkspaceStore: ObservableObject { return nil } - func controlFocus(pane: String) -> String? { + func controlFocus(pane: String, activateApp: Bool = true) -> String? { guard let key = Self.parsePaneKey(pane) else { return "bad-request" } guard let wsIdx = workspaces.firstIndex(where: { $0.id == key.workspace }), workspaces[wsIdx].panes[key.pane] != nil, @@ -2723,10 +2783,177 @@ final class WorkspaceStore: ObservableObject { workspaces[i].tabs[t].focusedPane = key.pane } } - NSApp.activate(ignoringOtherApps: true) + if activateApp { + NSApp.activate(ignoringOtherApps: true) + } + return nil + } + + func webRemoteWorkspacePayload() -> [[String: Any]] { + workspaces.map { workspace in + let focusedPane = workspace.selectedTab?.focusedPane + let panes: [[String: Any]] = workspace.panes.values + .sorted { $0.id.value < $1.id.value } + .map { pane in + let key = WorkspacePaneKey(workspace: workspace.id, pane: pane.id) + var value: [String: Any] = [ + "id": "\(workspace.id.uuidString):\(pane.id.value)", + "title": pane.title, + "ready": surfaceViews[key]?.hasLiveSurface ?? false, + "selected": workspace.id == selectedWorkspaceID && pane.id == focusedPane, + ] + if let cwd = pane.workingDirectory { value["cwd"] = cwd } + if let state = paneAgentState[key] { value["agent"] = state.status.rawValue } + return value + } + return [ + "id": workspace.id.uuidString, + "name": workspace.displayName, + "accent": workspace.accentHex, + "archived": workspace.archived, + "selected": workspace.id == selectedWorkspaceID, + "panes": panes, + ] + } + } + + func webRemoteThemePayload() -> [String: Any] { + let theme = Theme.current + let accentColor = accent + return [ + "id": theme.id, + "dark": theme.isDark, + "background": "#\(theme.background.rgbHex)", + "foreground": "#\(theme.foreground.rgbHex)", + "cursor": "#\(accentColor.rgbHex)", + "selectionBackground": "#\(accentColor.rgbHex)", + "selectionForeground": "#\(theme.foreground.rgbHex)", + "palette": theme.palette.map { "#\($0.rgbHex)" }, + "chrome": [ + "window": "#\(theme.bgWindow.rgbHex)", + "pane": "#\(theme.bgPane.rgbHex)", + "sidebar": "#\(theme.bgSidebar.rgbHex)", + "text1": "#\(theme.text1.rgbHex)", + "text3": "#\(theme.text3.rgbHex)", + "text4": "#\(theme.text4.rgbHex)", + "accent": "#\(accentColor.rgbHex)", + ], + ] + } + + func webRemoteBrandPayload() -> [String: Any]? { + guard let dataURL = WebRemoteBrandIcon.dataURL(for: appIconPreset) else { return nil } + return [ + "preset": appIconPreset.rawValue, + "dataURL": dataURL, + ] + } + + func webRemoteTerminalSnapshot(pane: String) -> WebRemoteTerminalSnapshotResult { + guard let key = Self.parsePaneKey(pane) else { return .failure("bad-request") } + guard paneExists(key) else { return .failure("unknown-pane") } + guard let view = surfaceViews[key], view.ensureLiveForWebRemoteControl() else { + return .failure("pane-not-ready") + } + webRemoteControlledPanes.insert(key) + guard let snapshot = view.webRemoteSnapshot() else { return .failure("pane-not-ready") } + return .success(snapshot) + } + + func webRemoteSetTerminalSize(pane: String, size: WebRemoteTerminalSize) -> String? { + guard let key = Self.parsePaneKey(pane) else { return "bad-request" } + guard paneExists(key) else { return "unknown-pane" } + guard let view = surfaceViews[key], view.hasLiveSurface else { return "pane-not-ready" } + view.setWebRemoteGridSize(size) + webRemoteControlledPanes.insert(key) + return nil + } + + func webRemoteReleaseTerminalSize(pane: String) { + guard let key = Self.parsePaneKey(pane) else { return } + webRemoteControlledPanes.remove(key) + surfaceViews[key]?.releaseWebRemoteGridSize() + } + + func isWebRemoteControlled(workspaceID: UUID, paneID: PaneID) -> Bool { + webRemoteControlledPanes.contains( + WorkspacePaneKey(workspace: workspaceID, pane: paneID) + ) + } + + func webRemoteSendInput(pane: String, data: Data) -> String? { + guard let key = Self.parsePaneKey(pane) else { return "bad-request" } + guard paneExists(key) else { return "unknown-pane" } + guard let view = surfaceViews[key], view.hasLiveSurface else { return "pane-not-ready" } + view.injectRemoteInput(data) return nil } + func webRemoteOpenProject(path: String) -> WebRemoteOpenProjectResult { + guard let standardized = WebRemoteProjectPath.resolveExistingDirectory(path) else { + return .failure("invalid-project-path") + } + return .success(openFolderWorkspace(standardized, addTabIfExisting: false)) + } + + func webRemoteCreateTerminal(workspace workspaceID: UUID) -> WebRemoteCreateTerminalResult { + guard let index = workspaces.firstIndex(where: { $0.id == workspaceID }) else { + return .failure("unknown-workspace") + } + guard !workspaces[index].archived else { + return .failure("workspace-archived") + } + + selectWorkspace(workspaceID) + newTab() + guard let pane = workspaces[index].selectedTab?.focusedPane else { + return .failure("terminal-not-ready") + } + return .success("\(workspaceID.uuidString):\(pane.value)") + } + + func webRemoteCloseTerminal( + pane: String, + confirmed: Bool + ) -> WebRemoteCloseTerminalResult { + guard let key = Self.parsePaneKey(pane) else { return .failure("bad-request") } + guard let workspaceIndex = workspaces.firstIndex(where: { $0.id == key.workspace }), + workspaces[workspaceIndex].panes[key.pane] != nil, + let tabIndex = workspaces[workspaceIndex].tabs.firstIndex(where: { + $0.root.leaves.contains(key.pane) + }) + else { return .failure("unknown-pane") } + + if paneNeedsCloseConfirmation(key), !confirmed { + return .confirmationRequired + } + + let tab = workspaces[workspaceIndex].tabs[tabIndex] + if tab.root.leaves.count == 1 { + guard workspaces[workspaceIndex].tabs.count > 1 else { + return .failure("last-terminal") + } + let wasSelected = workspaces[workspaceIndex].selectedTabID == tab.id + teardownTab(at: tabIndex, in: workspaceIndex, wsID: key.workspace) + if wasSelected { + let nextIndex = min(tabIndex, workspaces[workspaceIndex].tabs.count - 1) + workspaces[workspaceIndex].selectedTabID = workspaces[workspaceIndex].tabs[nextIndex].id + } + return .success + } + + let (newRoot, survivor) = Self.removeLeaf(tab.root, target: key.pane) + guard let newRoot else { return .failure("unknown-pane") } + workspaces[workspaceIndex].tabs[tabIndex].root = newRoot + teardownPane(key, in: workspaceIndex) + if tab.focusedPane == key.pane { + workspaces[workspaceIndex].tabs[tabIndex].focusedPane = survivor + ?? newRoot.leaves.first + ?? PaneID(value: 0) + } + return .success + } + func selectWorkspace(_ id: UUID) { selectedWorkspaceID = id acknowledgeCompletionIfNeeded(for: id) @@ -2855,17 +3082,22 @@ final class WorkspaceStore: ObservableObject { let panes = workspaces[i].tabs[index].root.leaves for pane in panes { let key = WorkspacePaneKey(workspace: wsID, pane: pane) - workspaces[i].panes.removeValue(forKey: pane) - surfaceViews.removeValue(forKey: key) - ScrollbackArchive.delete( - id: ScrollbackArchive.fileID(forPaneKey: "\(wsID.uuidString):\(pane.value)")) - paneAgentState.removeValue(forKey: key) - paneProcesses.removeValue(forKey: key) - clearDockBadge(for: key) + teardownPane(key, in: i) } workspaces[i].tabs.remove(at: index) } + private func teardownPane(_ key: WorkspacePaneKey, in workspaceIndex: Int) { + workspaces[workspaceIndex].panes.removeValue(forKey: key.pane) + surfaceViews.removeValue(forKey: key) + ScrollbackArchive.delete( + id: ScrollbackArchive.fileID(forPaneKey: "\(key.workspace.uuidString):\(key.pane.value)")) + paneAgentState.removeValue(forKey: key) + paneProcesses.removeValue(forKey: key) + webRemoteControlledPanes.remove(key) + clearDockBadge(for: key) + } + func selectTab(_ tabID: TabID) { guard let i = currentIndex, workspaces[i].tabs.contains(where: { $0.id == tabID }) else { return } @@ -3108,11 +3340,17 @@ final class WorkspaceStore: ObservableObject { /// dedupes instead of racing into a duplicate; the source is upgraded to /// `.localRepo` in place once git reports the repo root. private func openFolder(_ directory: String) { + openFolderWorkspace(directory, addTabIfExisting: true) + } + + @discardableResult + private func openFolderWorkspace(_ directory: String, addTabIfExisting: Bool) -> UUID { if let existingIndex = workspaces.firstIndex(where: { isAnchoredAt($0, directory) }) { if workspaces[existingIndex].archived { workspaces[existingIndex].archived = false } - selectWorkspace(workspaces[existingIndex].id) - newTab(cwd: directory) - return + let workspaceID = workspaces[existingIndex].id + selectWorkspace(workspaceID) + if addTabIfExisting { newTab(cwd: directory) } + return workspaceID } let dirName = (directory as NSString).lastPathComponent let wsID = appendWorkspace(cwd: directory, source: .plain, @@ -3124,6 +3362,7 @@ final class WorkspaceStore: ObservableObject { } await refreshGitStatus(for: wsID) } + return wsID } /// True if `ws` opens into `directory` — for deduping folder opens. Matches @@ -3920,6 +4159,17 @@ enum AppIconPreset: String, CaseIterable, Identifiable { } } +enum WebRemoteBrandIcon { + static func dataURL(for preset: AppIconPreset) -> String? { + guard let image = NSImage(named: preset.headerLogoAsset), + let tiff = image.tiffRepresentation, + let bitmap = NSBitmapImageRep(data: tiff), + let png = bitmap.representation(using: .png, properties: [:]) + else { return nil } + return "data:image/png;base64,\(png.base64EncodedString())" + } +} + @MainActor final class PaneActivityStore: ObservableObject { @Published var paneProcesses: [WorkspaceStore.WorkspacePaneKey: String] = [:] diff --git a/GlintTests/WebRemoteProtocolTests.swift b/GlintTests/WebRemoteProtocolTests.swift new file mode 100644 index 0000000..f66b914 --- /dev/null +++ b/GlintTests/WebRemoteProtocolTests.swift @@ -0,0 +1,500 @@ +import CryptoKit +import XCTest +@testable import Glint + +final class WebRemoteProtocolTests: XCTestCase { + func testTerminalSizeAcceptsBrowserGridWithinSafeBounds() { + XCTAssertEqual( + WebRemoteTerminalSize.parse(["columns": 132, "rows": 43]), + WebRemoteTerminalSize(columns: 132, rows: 43) + ) + } + + func testTerminalSizeRejectsMissingFractionalAndExtremeValues() { + XCTAssertNil(WebRemoteTerminalSize.parse(["columns": 132])) + XCTAssertNil(WebRemoteTerminalSize.parse(["columns": 132.5, "rows": 43])) + XCTAssertNil(WebRemoteTerminalSize.parse(["columns": 19, "rows": 43])) + XCTAssertNil(WebRemoteTerminalSize.parse(["columns": 132, "rows": 201])) + } + + func testSnapshotUsesTerminalNewlinesBetweenLogicalRows() { + let payload = String( + decoding: WebRemoteSnapshotPayload.make(ansi: "first\nsecond"), + as: UTF8.self + ) + + XCTAssertTrue(payload.hasSuffix("first\r\nsecond")) + } + + func testSnapshotRestoresGhosttyTerminalStateAfterPaintingGrid() throws { + let json = Data(#""" + { + "columns": 80, + "rows": 24, + "cursor": {"row": 5, "column": 7, "visible": false, "style": "bar", "blinking": true}, + "styles": [{ + "id": 0, + "foreground": "#FFFFFF", + "background": "#000000", + "bold": false, + "faint": false, + "italic": false, + "underline": false, + "blink": false, + "inverse": false, + "invisible": false, + "strikethrough": false, + "overline": false + }], + "row_spans": [{"row": 0, "column": 0, "style_id": 0, "cell_width": 1, "text": "X"}], + "row_wraps": [false], + "active_screen": "alternate", + "modes": [ + {"code": 1, "ansi": false, "on": true}, + {"code": 6, "ansi": false, "on": true}, + {"code": 2004, "ansi": false, "on": true} + ], + "scrolling_region": {"top": 2, "bottom": 20, "left": 3, "right": 70}, + "pty_output_seq": 41, + "pty_stream_safe": true, + "scrollback_rows": 0, + "scrollback_row_wraps": [], + "scrollback_spans": [] + } + """#.utf8) + + let snapshot = try XCTUnwrap( + GhosttySurfaceView.webRemoteSnapshot(fromRenderGrid: json, maxLines: 1000) + ) + XCTAssertEqual(snapshot.outputSequence, 41) + let text = String(decoding: snapshot.payload, as: UTF8.self) + let paint = try XCTUnwrap(text.range(of: "X")?.lowerBound) + let alternate = try XCTUnwrap(text.range(of: "\u{1b}[?1049h")?.lowerBound) + let applicationCursor = try XCTUnwrap(text.range(of: "\u{1b}[?1h")?.lowerBound) + let bracketedPaste = try XCTUnwrap(text.range(of: "\u{1b}[?2004h")?.lowerBound) + let scrollingRegion = try XCTUnwrap(text.range(of: "\u{1b}[3;21r")?.lowerBound) + let horizontalMargins = try XCTUnwrap(text.range(of: "\u{1b}[4;71s")?.lowerBound) + let cursor = try XCTUnwrap(text.range(of: "\u{1b}[4;5H")?.lowerBound) + + XCTAssertLessThan(alternate, paint) + XCTAssertLessThan(paint, applicationCursor) + XCTAssertLessThan(applicationCursor, bracketedPaste) + XCTAssertLessThan(bracketedPaste, scrollingRegion) + XCTAssertLessThan(scrollingRegion, horizontalMargins) + XCTAssertLessThan(horizontalMargins, cursor) + XCTAssertTrue(text.hasSuffix("\u{1b}[5 q\u{1b}[?25l")) + } + + func testSnapshotRejectsUnsafePTYParserBoundary() { + let json = Data(#""" + { + "styles": [], + "row_spans": [], + "scrollback_spans": [], + "scrollback_rows": 0, + "rows": 24, + "pty_output_seq": 41, + "pty_stream_safe": false + } + """#.utf8) + + XCTAssertNil(GhosttySurfaceView.webRemoteSnapshot(fromRenderGrid: json, maxLines: 1000)) + } + + func testTerminalOutputBufferCoalescesBytesAndRejectsOverflow() { + var buffer = WebRemoteOutputBuffer(byteLimit: 4) + + XCTAssertTrue(buffer.append(Data("ab".utf8), sequence: 1)) + XCTAssertTrue(buffer.append(Data("cd".utf8), sequence: 2)) + XCTAssertFalse(buffer.append(Data("e".utf8), sequence: 3)) + XCTAssertEqual(buffer.take(after: 0), Data("abcd".utf8)) + XCTAssertTrue(buffer.isEmpty) + } + + func testTerminalOutputBufferDropsChunksAlreadyIncludedInSnapshot() { + var buffer = WebRemoteOutputBuffer(byteLimit: 32) + + XCTAssertTrue(buffer.append(Data("before".utf8), sequence: 40)) + XCTAssertTrue(buffer.append(Data("included".utf8), sequence: 41)) + XCTAssertTrue(buffer.append(Data("after".utf8), sequence: 42)) + + XCTAssertEqual(buffer.take(after: 41), Data("after".utf8)) + XCTAssertTrue(buffer.isEmpty) + } + + func testTerminalOutputBufferPreservesSequenceBoundariesWhenCoalescingBatches() { + var ingress = WebRemoteOutputBuffer(byteLimit: 32) + XCTAssertTrue(ingress.append(Data("included".utf8), sequence: 41)) + XCTAssertTrue(ingress.append(Data("after".utf8), sequence: 42)) + + var pendingSelection = WebRemoteOutputBuffer(byteLimit: 32) + XCTAssertTrue(pendingSelection.append(contentsOf: ingress)) + + XCTAssertEqual(pendingSelection.take(after: 41), Data("after".utf8)) + } + + func testOutboundBufferKeepsSnapshotBeforeBufferedTerminalOutput() { + let snapshot = Data("snapshot".utf8) + var buffer = WebRemoteOutboundBuffer(maxQueuedOutputBytes: 4) + + buffer.enqueueMessage(snapshot) + XCTAssertTrue(buffer.enqueueTerminalOutput(Data("ab".utf8), pane: "pane-1")) + XCTAssertTrue(buffer.enqueueTerminalOutput(Data("cd".utf8), pane: "pane-1")) + XCTAssertFalse(buffer.enqueueTerminalOutput(Data("e".utf8), pane: "pane-1")) + + guard case let .message(message)? = buffer.next() else { + return XCTFail("Snapshot message must be sent first") + } + XCTAssertEqual(message, snapshot) + guard case let .terminalOutput(pane, output)? = buffer.next() else { + return XCTFail("Buffered terminal output must follow the snapshot") + } + XCTAssertEqual(pane, "pane-1") + XCTAssertEqual(output, Data("abcd".utf8)) + XCTAssertNil(buffer.next()) + } + + func testAccessTokenIsRandomHexAndConstantTimeMatcherChecksWholeValue() { + let token = WebRemoteAccessToken.generate() + + XCTAssertEqual(token.count, 64) + XCTAssertNotNil(UInt64(token.prefix(16), radix: 16)) + XCTAssertTrue(WebRemoteAccessToken.matches(token, expected: token)) + XCTAssertFalse(WebRemoteAccessToken.matches(nil, expected: token)) + XCTAssertFalse(WebRemoteAccessToken.matches(token + "0", expected: token)) + XCTAssertFalse(WebRemoteAccessToken.matches(String(token.dropLast()) + "0", expected: token)) + } + + func testAccessKeyPersistsUntilExplicitReset() { + let suite = "WebRemoteAccessKeyStoreTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suite) else { + return XCTFail("Could not create isolated defaults") + } + defer { defaults.removePersistentDomain(forName: suite) } + + let first = WebRemoteAccessKeyStore.loadOrCreate(defaults: defaults) + XCTAssertEqual(WebRemoteAccessKeyStore.loadOrCreate(defaults: defaults), first) + + let reset = WebRemoteAccessKeyStore.reset(defaults: defaults) + XCTAssertNotEqual(reset, first) + XCTAssertEqual(WebRemoteAccessKeyStore.loadOrCreate(defaults: defaults), reset) + } + + func testWebRemotePortPersistsUntilExplicitReset() { + let suite = "WebRemotePortStoreTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suite) else { + return XCTFail("Could not create isolated defaults") + } + defer { defaults.removePersistentDomain(forName: suite) } + + let initial = WebRemotePortStore.loadOrCreate(defaults: defaults) + XCTAssertEqual(initial.http, 43871) + XCTAssertEqual(initial.webSocket, 43872) + XCTAssertEqual(WebRemotePortStore.loadOrCreate(defaults: defaults), initial) + + let reset = WebRemotePortStore.reset(defaults: defaults, randomPort: { 52000 }) + XCTAssertEqual(reset.http, 52000) + XCTAssertEqual(reset.webSocket, 52001) + XCTAssertNotEqual(reset, initial) + XCTAssertEqual(WebRemotePortStore.loadOrCreate(defaults: defaults), reset) + } + + func testEveryAppIconPresetProvidesAWebRemotePNG() throws { + for preset in AppIconPreset.allCases { + let value = try XCTUnwrap(WebRemoteBrandIcon.dataURL(for: preset), preset.rawValue) + XCTAssertTrue(value.hasPrefix("data:image/png;base64,"), preset.rawValue) + let encoded = String(value.dropFirst("data:image/png;base64,".count)) + let png = try XCTUnwrap(Data(base64Encoded: encoded), preset.rawValue) + XCTAssertEqual(Array(png.prefix(8)), [137, 80, 78, 71, 13, 10, 26, 10], preset.rawValue) + } + } + + func testAccessKeyCanBeCopiedSeparatelyFromSessionURL() { + let value = "http://192.168.1.20:43871/#token=abc123" + + XCTAssertEqual(WebRemoteAccessURL.token(from: value), "abc123") + XCTAssertNil(WebRemoteAccessURL.token(from: "http://192.168.1.20:43871/")) + } + + func testRedactedURLStripsTokenFragmentForSafeCopying() { + XCTAssertEqual( + WebRemoteAccessURL.redacted(from: "http://192.168.1.20:43871/#token=abc123"), + "http://192.168.1.20:43871/" + ) + // A URL without a token fragment is returned unchanged. + XCTAssertEqual( + WebRemoteAccessURL.redacted(from: "http://192.168.1.20:43871/"), + "http://192.168.1.20:43871/" + ) + } + + func testHTTPRequestParsesGetAndStripsQuery() { + let data = Data("GET /app.js?v=1 HTTP/1.1\r\nHost: localhost\r\n\r\n".utf8) + + XCTAssertEqual( + WebRemoteHTTPRequest.parse(data), + WebRemoteHTTPRequest(method: .get, path: "/app.js") + ) + } + + func testHTTPRequestRejectsUnsupportedMethodAndMalformedTarget() { + XCTAssertNil(WebRemoteHTTPRequest.parse(Data("POST / HTTP/1.1\r\n\r\n".utf8))) + XCTAssertNil(WebRemoteHTTPRequest.parse(Data("GET relative HTTP/1.1\r\n\r\n".utf8))) + XCTAssertNil(WebRemoteHTTPRequest.parse(Data("not-http".utf8))) + } + + func testAssetsOnlyExposeBundledAllowlist() { + XCTAssertEqual(WebRemoteAssets.asset(for: "/")?.resource, "web-remote-index") + XCTAssertEqual(WebRemoteAssets.asset(for: "/xterm.mjs")?.contentType, "text/javascript; charset=utf-8") + XCTAssertEqual(WebRemoteAssets.asset(for: "/symbols-nerd-font-mono.ttf")?.contentType, "font/ttf") + XCTAssertNil(WebRemoteAssets.asset(for: "/../state.json")) + XCTAssertNil(WebRemoteAssets.asset(for: "/favicon.ico")) + } + + func testHeadResponseKeepsContentLengthWithoutBody() { + let body = Data("hello".utf8) + let response = WebRemoteHTTPResponse.make( + status: 200, + reason: "OK", + contentType: "text/plain", + body: body, + includeBody: false + ) + let text = String(decoding: response, as: UTF8.self) + + XCTAssertTrue(text.contains("Content-Length: 5\r\n")) + XCTAssertTrue(text.hasSuffix("\r\n\r\n")) + XCTAssertFalse(text.hasSuffix("hello")) + } + + func testProjectPathOnlyAcceptsExistingDirectories() throws { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("glint-web-remote-tests-\(UUID().uuidString)", isDirectory: true) + let file = root.appendingPathComponent("file.txt") + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + try Data("x".utf8).write(to: file) + defer { try? FileManager.default.removeItem(at: root) } + + XCTAssertEqual(WebRemoteProjectPath.resolveExistingDirectory(root.path), root.path) + XCTAssertNil(WebRemoteProjectPath.resolveExistingDirectory(file.path)) + XCTAssertNil(WebRemoteProjectPath.resolveExistingDirectory(root.appendingPathComponent("missing").path)) + XCTAssertNil(WebRemoteProjectPath.resolveExistingDirectory("relative/path")) + } + + @MainActor + func testRemoteCloseTargetsRequestedPaneAndRequiresConfirmationForBusyProcess() { + let workspaceID = UUID() + let closingPane = PaneID(value: 0) + let survivingPane = PaneID(value: 1) + let tab = WorkspaceTab( + id: TabID(value: 0), + name: nil, + root: .split( + direction: .horizontal, + ratio: 0.5, + a: .leaf(closingPane), + b: .leaf(survivingPane) + ), + focusedPane: survivingPane + ) + let workspace = Workspace( + id: workspaceID, + name: "Remote Close", + userNamed: true, + accentHex: "5E5CE6", + symbol: "R", + tabs: [tab], + selectedTabID: tab.id, + nextTabSeq: 1, + panes: [ + closingPane: Pane(id: closingPane, title: "vim"), + survivingPane: Pane(id: survivingPane, title: "zsh"), + ], + nextPaneSeq: 2 + ) + let store = WorkspaceStore(activity: PaneActivityStore()) + store.workspaces = [workspace] + store.selectedWorkspaceID = workspaceID + store.paneProcesses[ + WorkspaceStore.WorkspacePaneKey(workspace: workspaceID, pane: closingPane) + ] = "vim" + let handle = "\(workspaceID.uuidString):\(closingPane.value)" + + XCTAssertEqual( + store.webRemoteCloseTerminal(pane: handle, confirmed: false), + .confirmationRequired + ) + XCTAssertNotNil(store.workspaces[0].panes[closingPane]) + + XCTAssertEqual( + store.webRemoteCloseTerminal(pane: handle, confirmed: true), + .success + ) + XCTAssertNil(store.workspaces[0].panes[closingPane]) + XCTAssertNotNil(store.workspaces[0].panes[survivingPane]) + XCTAssertEqual(store.workspaces[0].tabs[0].root.leaves, [survivingPane]) + XCTAssertEqual(store.workspaces[0].tabs[0].focusedPane, survivingPane) + } + + @MainActor + func testRemoteCloseRejectsWorkspaceLastTerminal() { + let workspace = Workspace.fresh(name: "Only", accentHex: "5E5CE6", symbol: "O") + let pane = try! XCTUnwrap(workspace.selectedTab?.focusedPane) + let store = WorkspaceStore(activity: PaneActivityStore()) + store.workspaces = [workspace] + store.selectedWorkspaceID = workspace.id + + XCTAssertEqual( + store.webRemoteCloseTerminal( + pane: "\(workspace.id.uuidString):\(pane.value)", + confirmed: true + ), + .failure("last-terminal") + ) + XCTAssertNotNil(store.workspaces[0].panes[pane]) + } + + // MARK: - Challenge-response crypto (must match the JS client byte-for-byte) + + private func hexString(_ data: Data) -> String { + data.map { String(format: "%02x", $0) }.joined() + } + + private func hexString(_ key: SymmetricKey) -> String { + key.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02x", $0) }.joined() + } + + func testTokenKeyDecodesHexAndRejectsAnythingElse() { + XCTAssertNil(WebRemoteCrypto.tokenKey(from: "0011ff")) + // Exactly 64 lowercase hex chars → 32 bytes. + let full = String(repeating: "ab", count: 32) + XCTAssertEqual(WebRemoteCrypto.tokenKey(from: full)?.count, 32) + // Uppercase, wrong length, non-hex, and empty all fail (fail closed). + XCTAssertNil(WebRemoteCrypto.tokenKey(from: String(repeating: "ABCD", count: 16))) + XCTAssertNil(WebRemoteCrypto.tokenKey(from: String(repeating: "00", count: 31))) + XCTAssertNil(WebRemoteCrypto.tokenKey(from: String(repeating: "zz", count: 32))) + XCTAssertNil(WebRemoteCrypto.tokenKey(from: "")) + } + + /// Pins the Swift derivation to the exact bytes the JS client (vendored + /// noble) produces for the same inputs — the wire-compat contract. If this + /// changes, the browser and the Mac can no longer talk. + func testCryptoDerivationMatchesClientVector() { + let token = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" + let challenge = Data((0 ..< 32).map { UInt8($0) }) + let key = try! XCTUnwrap(WebRemoteCrypto.tokenKey(from: token)) + + XCTAssertEqual( + hexString(WebRemoteCrypto.proof(tokenKey: key, challenge: challenge)), + "4bab2b0021f6ea13cfa4a9b0ca0741d025ccd291c5b98dbf43480e9ba086120b" + ) + let keys = WebRemoteCrypto.sessionKeys(tokenKey: key, challenge: challenge) + XCTAssertEqual(hexString(keys.c2s), "e8c4e3f3f90401c6afdbc05ac630f5fb2ab4bea095e695e13551a45015f253c2") + XCTAssertEqual(hexString(keys.s2c), "0d95713984b1723faefc9da27063fb792860e30b29648123dd1ecafae17df0d3") + } + + func testProofAndSessionKeysAreDeterministicAndChallengeBound() { + let key = WebRemoteCrypto.tokenKey(from: String(repeating: "9c", count: 32))! + let challengeA = Data(repeating: 0x01, count: 32) + let challengeB = Data(repeating: 0x02, count: 32) + + // Same inputs → same outputs. + XCTAssertEqual( + WebRemoteCrypto.proof(tokenKey: key, challenge: challengeA), + WebRemoteCrypto.proof(tokenKey: key, challenge: challengeA) + ) + // Different challenge → different proof and different keys (no leakage + // across connections). + XCTAssertNotEqual( + WebRemoteCrypto.proof(tokenKey: key, challenge: challengeA), + WebRemoteCrypto.proof(tokenKey: key, challenge: challengeB) + ) + let a = WebRemoteCrypto.sessionKeys(tokenKey: key, challenge: challengeA) + let b = WebRemoteCrypto.sessionKeys(tokenKey: key, challenge: challengeB) + XCTAssertNotEqual(hexString(a.c2s), hexString(b.c2s)) + XCTAssertNotEqual(hexString(a.s2c), hexString(b.s2c)) + // Direction keys are themselves distinct. + XCTAssertNotEqual(hexString(a.c2s), hexString(a.s2c)) + } + + func testConstantTimeEqualsHandlesLengthAndContent() { + XCTAssertTrue(WebRemoteCrypto.constantTimeEquals(Data([1, 2, 3]), Data([1, 2, 3]))) + XCTAssertFalse(WebRemoteCrypto.constantTimeEquals(Data([1, 2, 3]), Data([1, 2, 4]))) + XCTAssertFalse(WebRemoteCrypto.constantTimeEquals(Data([1, 2, 3]), Data([1, 2]))) + XCTAssertFalse(WebRemoteCrypto.constantTimeEquals(Data([1, 2, 3]), Data([]))) + XCTAssertTrue(WebRemoteCrypto.constantTimeEquals(Data(), Data())) + } + + func testNonceCounterRoundTripsAndStaysMonotonic() { + XCTAssertEqual(WebRemoteCrypto.nonce(for: 0), Data(count: 12)) + XCTAssertEqual(WebRemoteCrypto.counter(fromNonce: WebRemoteCrypto.nonce(for: 0)), 0) + XCTAssertEqual(WebRemoteCrypto.counter(fromNonce: WebRemoteCrypto.nonce(for: 42)), 42) + // The low byte advances first (big-endian) and nonces never repeat. + XCTAssertEqual(WebRemoteCrypto.nonce(for: 1).last, 0x01) + var seen = Set() + for counter in 0 ..< 1_000 { + let nonce = WebRemoteCrypto.nonce(for: UInt64(counter)) + XCTAssertEqual(nonce.count, 12) + XCTAssertTrue(seen.insert(nonce).inserted, "nonce reused at counter \(counter)") + } + XCTAssertNil(WebRemoteCrypto.counter(fromNonce: Data(count: 8))) + } + + func testSealAndOpenRoundTripInEachDirection() { + let key = WebRemoteCrypto.tokenKey(from: String(repeating: "ab", count: 32))! + let challenge = Data(repeating: 0x07, count: 32) + let keys = WebRemoteCrypto.sessionKeys(tokenKey: key, challenge: challenge) + let plaintext = Data("the quick brown fox".utf8) + + for (label, sendKey, recvKey) in [("c2s", keys.c2s, keys.c2s), ("s2c", keys.s2c, keys.s2c)] { + guard let frame = WebRemoteCrypto.sealFrame(plaintext: plaintext, key: sendKey, counter: 5) else { + return XCTFail("seal failed for \(label)") + } + // Frame = nonce(12) || ciphertext || tag(16). + XCTAssertEqual(frame.count, 12 + plaintext.count + 16) + XCTAssertEqual( + WebRemoteCrypto.counter(fromNonce: frame.prefix(12)), + 5, + "nonce in frame must encode the counter" + ) + let body = frame.subdata(in: 12 ..< frame.count) + XCTAssertEqual( + WebRemoteCrypto.openFrame(nonce: frame.prefix(12), body: body, key: recvKey), + plaintext, + "round-trip failed for \(label)" + ) + } + } + + func testOpenRejectsWrongKeyTamperingAndTruncation() { + let key = WebRemoteCrypto.tokenKey(from: String(repeating: "ab", count: 32))! + let otherKey = WebRemoteCrypto.tokenKey(from: String(repeating: "cd", count: 32))! + let challenge = Data(repeating: 0x07, count: 32) + let keys = WebRemoteCrypto.sessionKeys(tokenKey: key, challenge: challenge) + let other = WebRemoteCrypto.sessionKeys(tokenKey: otherKey, challenge: challenge) + let plaintext = Data("secret".utf8) + + let frame = WebRemoteCrypto.sealFrame(plaintext: plaintext, key: keys.s2c, counter: 0)! + let nonce = frame.prefix(12) + let body = frame.subdata(in: 12 ..< frame.count) + + // Wrong key (e.g. wrong token) → auth tag mismatch. + XCTAssertNil(WebRemoteCrypto.openFrame(nonce: nonce, body: body, key: other.s2c)) + // Tamper one ciphertext byte → rejected. + var tampered = body + tampered[0] ^= 0xff + XCTAssertNil(WebRemoteCrypto.openFrame(nonce: nonce, body: tampered, key: keys.s2c)) + // Truncated frame → rejected. + XCTAssertNil(WebRemoteCrypto.openFrame(nonce: nonce, body: body.prefix(10), key: keys.s2c)) + // Wrong-length nonce → rejected. + XCTAssertNil(WebRemoteCrypto.openFrame(nonce: Data(count: 8), body: body, key: keys.s2c)) + } + + func testCryptoAssetIsRegisteredForTheClient() { + XCTAssertEqual( + WebRemoteAssets.asset(for: "/crypto.mjs")?.contentType, + "text/javascript; charset=utf-8" + ) + XCTAssertNil(WebRemoteAssets.asset(for: "/crypto.js")) + } +} diff --git a/GlintTests/WebRemoteServerIntegrationTests.swift b/GlintTests/WebRemoteServerIntegrationTests.swift new file mode 100644 index 0000000..0892370 --- /dev/null +++ b/GlintTests/WebRemoteServerIntegrationTests.swift @@ -0,0 +1,368 @@ +import CryptoKit +import Network +import XCTest +@testable import Glint + +/// End-to-end tests that drive `WebRemoteServer.shared` over real sockets on the +/// loopback interface. They cover the HTTP asset layer (including the allowlist +/// and HEAD handling) and the WebSocket authentication flow (including the +/// exponential backoff that throttles online token guessing). +/// +/// These tests bind the project's persisted HTTP/WebSocket port pair on +/// 127.0.0.1, so they will conflict with a concurrently running Glint whose web +/// remote is enabled. The server is started fresh in `setUp` and stopped in +/// `tearDown`; test methods run serially within the class. +final class WebRemoteServerIntegrationTests: XCTestCase { + private var urlSession: URLSession! + private var readyToken: String? + private var httpOrigin: String? + private var webSocketURL: URL? + + override func setUp() async throws { + try await super.setUp() + urlSession = URLSession(configuration: .ephemeral) + readyToken = nil + httpOrigin = nil + webSocketURL = nil + + let ready = expectation(description: "WebRemoteServer reports .ready") + WebRemoteServer.shared.setStatusHandler { [weak self] status in + guard case let .ready(urls) = status, + let url = urls.first, + let token = WebRemoteAccessURL.token(from: url), + let components = URLComponents(string: url), + let host = components.host, + let httpPort = components.port + else { return } + self?.readyToken = token + self?.httpOrigin = "http://\(host):\(httpPort)" + self?.webSocketURL = URL(string: "ws://\(host):\(httpPort + 1)/control") + ready.fulfill() + } + // Bind to loopback only — never touch a real NIC from tests. + WebRemoteServer.shared.setListenInterface(WebRemoteListenTarget.loopback) + WebRemoteServer.shared.start() + try await fulfillment(of: [ready], timeout: 10) + XCTAssertNotNil(readyToken, "Server should expose an access token in its ready URL") + XCTAssertNotNil(httpOrigin) + XCTAssertNotNil(webSocketURL) + } + + override func tearDown() async throws { + WebRemoteServer.shared.stop() + // stop() is asynchronous on the server queue; let NWListener cancel. + try? await Task.sleep(nanoseconds: 400_000_000) + urlSession.invalidateAndCancel() + urlSession = nil + try await super.tearDown() + } + + // MARK: - HTTP layer + + func testHTTPServesIndexHtml() async throws { + let (data, http) = try await request("/") + XCTAssertEqual(http.statusCode, 200) + XCTAssertNotNil(http.value(forHTTPHeaderField: "Content-Type")?.range(of: "text/html")) + XCTAssertFalse(data.isEmpty) + } + + func testHTTPServesAppJavaScript() async throws { + let (data, http) = try await request("/app.js") + XCTAssertEqual(http.statusCode, 200) + XCTAssertNotNil(http.value(forHTTPHeaderField: "Content-Type")?.range(of: "text/javascript")) + XCTAssertFalse(data.isEmpty) + } + + func testHTTPServesVendoredXterm() async throws { + let (data, http) = try await request("/xterm.mjs") + XCTAssertEqual(http.statusCode, 200) + XCTAssertFalse(data.isEmpty) + } + + func testHTTPReturns404ForUnknownAsset() async throws { + let (data, http) = try await request("/favicon.ico") + XCTAssertEqual(http.statusCode, 404) + XCTAssertFalse(data.isEmpty, "404 should still carry a short text body") + } + + func testHTTPHeadOmitsBodyButKeepsContentLength() async throws { + let (data, http) = try await request("/app.js", method: "HEAD") + XCTAssertEqual(http.statusCode, 200) + XCTAssertTrue(data.isEmpty, "HEAD must not return a body") + XCTAssertNotNil(http.value(forHTTPHeaderField: "Content-Length")) + } + + // MARK: - WebSocket authentication (challenge-response + AES-GCM) + + func testWebSocketRejectsWrongProofThenAcceptsCorrectProof() async throws { + let token = try XCTUnwrap(readyToken) + let tokenKey = try XCTUnwrap(WebRemoteCrypto.tokenKey(from: token)) + let task = makeWebSocket() + defer { task.cancel(with: .goingAway, reason: nil) } + + // The server issues a per-connection challenge on connect. + let challenge = try await receiveAuthChallenge(task) + + // Wrong proof → plaintext `unauthorized` after the backoff window. The + // token itself never crosses the wire. + try await send(task, ["type": "authenticate", "proof": wrongProofBase64]) + let bad = try await receiveJSON(task) + XCTAssertEqual(bad["type"] as? String, "error") + XCTAssertEqual(bad["code"] as? String, "unauthorized") + + // Correct proof → the first reply is an *encrypted* frame. Decrypting it + // to `{"type":"authenticated"}` proves the session keys agree. + let proof = WebRemoteCrypto.proof(tokenKey: tokenKey, challenge: challenge) + try await send(task, ["type": "authenticate", "proof": proof.base64EncodedString()]) + let keys = WebRemoteCrypto.sessionKeys(tokenKey: tokenKey, challenge: challenge) + let good = try await receiveEncryptedJSON(task, key: keys.s2c) + // `sendState` no-ops without a WorkspaceStore, so only `authenticated` + // arrives — enough to prove the proof was accepted and the frame encrypted. + XCTAssertEqual(good["type"] as? String, "authenticated") + } + + func testAuthenticationBackoffGrowsAcrossConsecutiveFailures() async throws { + let task = makeWebSocket() + defer { task.cancel(with: .goingAway, reason: nil) } + _ = try await receiveAuthChallenge(task) + + func timedFailure() async throws -> TimeInterval { + let start = Date() + try await send(task, ["type": "authenticate", "proof": wrongProofBase64]) + let reply = try await receiveJSON(task) + XCTAssertEqual(reply["code"] as? String, "unauthorized") + return Date().timeIntervalSince(start) + } + + let first = try await timedFailure() // ~0.25s + let second = try await timedFailure() // ~0.5s + + // Exponential: the second failure must wait materially longer than the + // first, yet stay far below the 16s cap. Loose thresholds absorb CI + // scheduler jitter (expected ratio is ~2x). + XCTAssertGreaterThan(second, first) + XCTAssertGreaterThanOrEqual(second, first * 1.4) + XCTAssertLessThan(second, 3.0) + } + + func testAuthenticationBackoffSpansSeparateConnectionsFromSameSource() async throws { + let firstTask = makeWebSocket() + let secondTask = makeWebSocket() + defer { + firstTask.cancel(with: .goingAway, reason: nil) + secondTask.cancel(with: .goingAway, reason: nil) + } + _ = try await receiveAuthChallenge(firstTask) + _ = try await receiveAuthChallenge(secondTask) + + let firstStart = Date() + try await send(firstTask, ["type": "authenticate", "proof": wrongProofBase64]) + let firstReply = try await receiveJSON(firstTask) + XCTAssertEqual(firstReply["code"] as? String, "unauthorized") + let first = Date().timeIntervalSince(firstStart) + + let secondStart = Date() + try await send(secondTask, ["type": "authenticate", "proof": wrongProofBase64]) + let secondReply = try await receiveJSON(secondTask) + XCTAssertEqual(secondReply["code"] as? String, "unauthorized") + let second = Date().timeIntervalSince(secondStart) + + XCTAssertGreaterThan(second, first) + XCTAssertGreaterThanOrEqual(second, first * 1.4) + XCTAssertLessThan(second, 3.0) + } + + func testAuthenticationThrottleSpansConnectionsFromSameSource() { + var throttle = WebRemoteAuthThrottle(expirySeconds: 60) + + XCTAssertEqual(throttle.recordFailure(for: "127.0.0.1", now: 1_000), 1) + XCTAssertEqual( + throttle.recordFailure(for: "127.0.0.1", now: 1_001), + 2, + "A new connection from the same source must not reset the backoff" + ) + XCTAssertEqual(throttle.recordFailure(for: "192.0.2.10", now: 1_001), 1) + XCTAssertEqual( + throttle.recordFailure(for: "127.0.0.1", now: 1_061), + 1, + "Idle failure history should expire" + ) + } + + private var wrongProofBase64: String { + Data(repeating: 0, count: WebRemoteCrypto.challengeLength).base64EncodedString() + } + + // MARK: - Pure-function behaviour + + func testAuthBackoffCurveIsMonotonicAndCapped() { + let expected: [TimeInterval] = [0.25, 0.5, 1, 2, 4, 8, 16, 16] + for (count, want) in zip(1 ... 8, expected) { + XCTAssertEqual( + WebRemoteServer.authBackoffSeconds(forFailures: count), + want, + accuracy: 0.0001, + "count \(count)" + ) + } + // Out-of-range inputs clamp, never grow unbounded. + XCTAssertEqual(WebRemoteServer.authBackoffSeconds(forFailures: 0), 0.25, accuracy: 0.0001) + XCTAssertEqual(WebRemoteServer.authBackoffSeconds(forFailures: 1_000), 16, accuracy: 0.0001) + } + + func testClientAdmissionCapsTotalAndUnauthenticatedConnections() { + XCTAssertTrue(WebRemoteServer.allowsNewClient(total: 0, unauthenticated: 0)) + XCTAssertTrue( + WebRemoteServer.allowsNewClient( + total: WebRemoteServer.maxClientConnections - 1, + unauthenticated: WebRemoteServer.maxUnauthenticatedConnections - 1 + ) + ) + XCTAssertFalse( + WebRemoteServer.allowsNewClient( + total: WebRemoteServer.maxClientConnections, + unauthenticated: 0 + ) + ) + XCTAssertFalse( + WebRemoteServer.allowsNewClient( + total: 0, + unauthenticated: WebRemoteServer.maxUnauthenticatedConnections + ) + ) + } + + func testAuthSourceKeyIgnoresEphemeralPort() throws { + let first = NWEndpoint.hostPort( + host: "127.0.0.1", + port: try XCTUnwrap(NWEndpoint.Port(rawValue: 12_001)) + ) + let second = NWEndpoint.hostPort( + host: "127.0.0.1", + port: try XCTUnwrap(NWEndpoint.Port(rawValue: 12_002)) + ) + + XCTAssertEqual( + WebRemoteServer.authSourceKey(for: first), + WebRemoteServer.authSourceKey(for: second) + ) + } + + func testLANListenTargetsRequireActiveAttackWarning() { + XCTAssertFalse( + WebRemoteListenTarget.requiresActiveAttackWarning(WebRemoteListenTarget.loopback) + ) + XCTAssertTrue( + WebRemoteListenTarget.requiresActiveAttackWarning(WebRemoteListenTarget.any) + ) + XCTAssertTrue(WebRemoteListenTarget.requiresActiveAttackWarning("en0")) + } + + func testListenTargetBindAddressResolvesSpecialCases() { + XCTAssertEqual(WebRemoteListenTarget.bindAddress(for: WebRemoteListenTarget.loopback), "127.0.0.1") + XCTAssertNil(WebRemoteListenTarget.bindAddress(for: WebRemoteListenTarget.any)) + XCTAssertNil( + WebRemoteListenTarget.bindAddress(for: "glint-definitely-not-an-interface"), + "An unknown interface name must not resolve to a bind address" + ) + } + + func testVanishedSelectedInterfaceFailsLoudly() async throws { + // A user-chosen NIC that no longer exists must surface `.failed` — never + // silently fall back to a 0.0.0.0 wildcard bind (that would widen + // exposure, the opposite of picking a NIC). + WebRemoteServer.shared.stop() + try? await Task.sleep(nanoseconds: 400_000_000) + + let failed = expectation(description: "vanished interface reports .failed") + WebRemoteServer.shared.setStatusHandler { status in + if case .failed = status { failed.fulfill() } + } + WebRemoteServer.shared.setListenInterface("glint-definitely-not-an-interface") + WebRemoteServer.shared.start() + try await fulfillment(of: [failed], timeout: 10) + } + + // MARK: - Helpers + + private func request(_ path: String, method: String = "GET") async throws -> (Data, HTTPURLResponse) { + let origin = try XCTUnwrap(httpOrigin) + var req = URLRequest(url: try XCTUnwrap(URL(string: origin + path))) + req.httpMethod = method + req.timeoutInterval = 5 + let (data, response) = try await urlSession.data(for: req) + return (data, try XCTUnwrap(response as? HTTPURLResponse)) + } + + private func makeWebSocket() -> URLSessionWebSocketTask { + let task = urlSession.webSocketTask(with: webSocketURL!) + task.resume() + return task + } + + private func send(_ task: URLSessionWebSocketTask, _ object: [String: Any]) async throws { + let text = String(data: try JSONSerialization.data(withJSONObject: object), encoding: .utf8) ?? "{}" + try await task.send(.string(text)) + } + + /// Receive one raw message, bounded by a timeout so a misbehaving server + /// fails the test instead of hanging it. + private func receiveMessage( + _ task: URLSessionWebSocketTask, + timeoutSeconds: UInt64 = 6 + ) async throws -> URLSessionWebSocketTask.Message { + try await withThrowingTaskGroup(of: URLSessionWebSocketTask.Message.self) { group in + group.addTask { try await task.receive() } + group.addTask { + try await Task.sleep(nanoseconds: timeoutSeconds * 1_000_000_000) + throw URLError(.timedOut) + } + guard let result = try await group.next() else { throw URLError(.timedOut) } + group.cancelAll() + return result + } + } + + private func receiveJSON(_ task: URLSessionWebSocketTask) async throws -> [String: Any] { + switch try await receiveMessage(task) { + case let .string(text): + return try XCTUnwrap(JSONSerialization.jsonObject(with: Data(text.utf8)) as? [String: Any]) + case let .data(data): + return try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any]) + @unknown default: + throw URLError(.badServerResponse) + } + } + + /// The server sends the plaintext challenge as soon as the socket opens. + private func receiveAuthChallenge(_ task: URLSessionWebSocketTask) async throws -> Data { + switch try await receiveMessage(task) { + case let .string(text): + let object = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(text.utf8)) as? [String: Any]) + XCTAssertEqual(object["type"] as? String, "auth-challenge") + return try XCTUnwrap(Data(base64Encoded: XCTUnwrap(object["challenge"] as? String))) + case .data: + throw URLError(.badServerResponse) + @unknown default: + throw URLError(.badServerResponse) + } + } + + /// Receive one encrypted binary frame, decrypt it under `key`, and decode + /// the plaintext JSON. Closes the loop on the server's encrypt path. + private func receiveEncryptedJSON( + _ task: URLSessionWebSocketTask, + key: SymmetricKey + ) async throws -> [String: Any] { + let data: Data + switch try await receiveMessage(task) { + case let .data(value): data = value + case .string: throw URLError(.badServerResponse) + @unknown default: throw URLError(.badServerResponse) + } + let nonce = data.prefix(WebRemoteCrypto.nonceLength) + let body = data.subdata(in: WebRemoteCrypto.nonceLength ..< data.count) + let plaintext = try XCTUnwrap(WebRemoteCrypto.openFrame(nonce: nonce, body: body, key: key)) + return try XCTUnwrap(JSONSerialization.jsonObject(with: plaintext) as? [String: Any]) + } +} diff --git a/ghostty b/ghostty index 0f86761..81f8da2 160000 --- a/ghostty +++ b/ghostty @@ -1 +1 @@ -Subproject commit 0f86761c0df7e9de8a11ec18bd2cb401f2fcc606 +Subproject commit 81f8da23679aa86988ab40b866bcbcf0a5bef4d5