forked from phcode-dev/staging.phcode.dev
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.js
More file actions
1 lines (1 loc) · 18.8 KB
/
Copy pathmain.js
File metadata and controls
1 lines (1 loc) · 18.8 KB
1
define(function(require,exports,module){if(!Phoenix.isNativeApp)return;const AppInit=require("utils/AppInit"),CommandManager=require("command/CommandManager"),WorkspaceManager=require("view/WorkspaceManager"),ProjectManager=require("project/ProjectManager"),ExtensionUtils=require("utils/ExtensionUtils"),Metrics=require("utils/Metrics"),NodeConnector=require("NodeConnector"),Mustache=require("thirdparty/mustache/mustache"),Dialogs=require("widgets/Dialogs"),DefaultDialogs=require("widgets/DefaultDialogs"),Strings=require("strings"),StringUtils=require("utils/StringUtils"),Menus=require("command/Menus"),Commands=require("command/Commands"),KeyBindingManager=require("command/KeyBindingManager"),NotificationUI=require("widgets/NotificationUI"),TerminalInstance=require("./TerminalInstance"),ShellProfiles=require("./ShellProfiles"),panelHTML=require("text!./terminal-panel.html");ExtensionUtils.loadStyleSheet(module,"../../thirdparty/xterm/xterm.css");const CMD_VIEW_TERMINAL=Commands.VIEW_TERMINAL,CMD_NEW_TERMINAL="terminal.new",CMD_TERMINAL_COPY="terminal.copy",CMD_TERMINAL_PASTE="terminal.paste",CMD_TERMINAL_CLEAR="terminal.clear",TERMINAL_CONTEXT_MENU_ID="terminal-context-menu",PANEL_ID="terminal-panel",PANEL_MIN_SIZE=100,SHELL_NAMES=new Set(["bash","zsh","fish","sh","dash","ksh","csh","tcsh","pwsh","powershell","cmd.exe","nu","elvish","xonsh","login","bash.exe","pwsh.exe","powershell.exe","nu.exe","fish.exe","elvish.exe","xonsh.exe","wsl.exe"]);function _isShellProcess(processName){if(!processName)return!0;const basename=processName.split("/").pop().split("\\").pop().replace(/^-/,"");return SHELL_NAMES.has(basename)}let panel=null,nodeConnector=null,terminalInstances=[],activeTerminalId=null,processInfo={},originalDefaultShellName=null,_focusToastShown=!1,_clearHintShown=!1,$panel,$contentArea,$shellDropdown,$flyoutList;function _initNodeConnector(){nodeConnector=NodeConnector.createNodeConnector("phoenix_terminal",exports)}function _createPanel(){const templateVars={Strings:{CMD_NEW_TERMINAL:"New Terminal",TERMINAL_CLEAR:"Clear",TERMINAL_KILL:"Kill",CMD_HIDE_TERMINAL:"Close Panel"}};$panel=$(Mustache.render(panelHTML,templateVars)),(panel=WorkspaceManager.createBottomPanel(PANEL_ID,$panel,PANEL_MIN_SIZE,void 0,{iconSvg:"styles/images/panel-icon-terminal.svg"})).focus=function(){const active=_getActiveTerminal();return!!active&&(active.focus(),!0)},$contentArea=$panel.find(".terminal-content-area"),$shellDropdown=$panel.find(".terminal-shell-dropdown"),$flyoutList=$panel.find(".terminal-flyout-list"),$contentArea.on("contextmenu",function(e){e.preventDefault(),terminalContextMenu.open(e)}),$panel.find(".terminal-flyout-new-btn").on("click",function(e){e.stopPropagation(),_createNewTerminal()}),$panel.find(".terminal-flyout-dropdown-btn").on("click",_onDropdownButtonClick),_setupPhoenixShortcuts(),$panel.find(".terminal-tab-bar").on("mouseenter",_refreshAllProcesses),$panel.find(".terminal-tab-bar").on("focusin",_refreshAllProcesses),WorkspaceManager.on("workspaceUpdateLayout",_handleResize);const PanelView=require("view/PanelView");PanelView.on(PanelView.EVENT_PANEL_SHOWN,function(_event,panelId){if(panelId===PANEL_ID){_updateTabBarMode();const active=_getActiveTerminal();active&&(active.handleResize(),active.focus()),_showFocusHintToast(),Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"panel","open")}});const observer=new MutationObserver(function(){_updateAllThemes()});observer.observe(document.body,{attributes:!0,attributeFilter:["class"]})}function _populateShellDropdown(){const shells=ShellProfiles.getShells(),defaultShell=ShellProfiles.getDefaultShell();$shellDropdown.empty();for(const shell of shells){const isSelected=defaultShell&&defaultShell.name===shell.name,$check=$('<span class="shell-check"></span>');isSelected&&$check.append('<i class="fa-solid fa-check"></i>');const $item=$('<div class="shell-option"></div>').attr("data-shell-name",shell.name).append($check).append($("<span></span>").text(shell.name));$item.on("click",function(){_hideShellDropdown(),ShellProfiles.setDefaultShell(shell.name),_populateShellDropdown(),_updateNewTerminalButtonLabel(),Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"pick",_shellMetricLabel(shell.name)),_createNewTerminalWithShell(shell)}),$shellDropdown.append($item)}}function _updateNewTerminalButtonLabel(){const defaultShell=ShellProfiles.getDefaultShell(),isOriginal=defaultShell&&defaultShell.name===originalDefaultShellName,label=!defaultShell||isOriginal?"Terminal":defaultShell.name;$panel.find(".terminal-btn-label").text(label),$panel.find(".terminal-flyout-new-btn").attr("title",label)}function _showShellDropdown(){const $body=$panel.find(".terminal-body"),$actions=$panel.find(".terminal-flyout-actions"),actionsRect=$actions[0].getBoundingClientRect(),bodyRect=$body[0].getBoundingClientRect();$shellDropdown.appendTo($body),$shellDropdown.css({position:"absolute",bottom:bodyRect.bottom-actionsRect.top+"px",right:"0",left:"auto",top:"auto"}),$shellDropdown.removeClass("forced-hidden"),setTimeout(function(){$(document).one("click",_hideShellDropdown)},0)}function _hideShellDropdown(){$shellDropdown.addClass("forced-hidden")}function _onDropdownButtonClick(e){e.stopPropagation(),$shellDropdown.hasClass("forced-hidden")?(_populateShellDropdown(),_showShellDropdown()):_hideShellDropdown()}async function _createNewTerminal(cwdOverride){const shell=ShellProfiles.getDefaultShell();return _createNewTerminalWithShell(shell,cwdOverride)}function _toNativePath(vfsPath){let cwd=vfsPath;const tauriPrefix=Phoenix.VFS.getTauriDir();return cwd.startsWith(tauriPrefix)&&(cwd=Phoenix.fs.getTauriPlatformPath(cwd)),cwd.length>1&&(cwd.endsWith("/")||cwd.endsWith("\\"))&&(cwd=cwd.slice(0,-1)),cwd}function _shellMetricLabel(shellName){if(!shellName)return"unknown";let n=String(shellName).toLowerCase();return n.endsWith(".exe")&&(n=n.slice(0,-4)),"powershell"===n&&(n="pwsh"),n.length>8&&(n=n.slice(0,8)),n||"unknown"}async function _createNewTerminalWithShell(shell,cwdOverride){if(!shell)return console.error("Terminal: No shell available"),void Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"new","noShell");let cwd;if(Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"new",_shellMetricLabel(shell.name)),cwdOverride)cwd=_toNativePath(cwdOverride);else{const projectRoot=ProjectManager.getProjectRoot();projectRoot&&(cwd=_toNativePath(projectRoot.fullPath))}const instance=new TerminalInstance(nodeConnector,shell,cwd);instance.onTitleChanged=_onTerminalTitleChanged,instance.onProcessExit=_onTerminalProcessExit,instance.create($contentArea),terminalInstances.push(instance);const count=terminalInstances.length,tabsBucket=1===count?"one":count<=4?"LTE4":count<=9?"LTE9":"GT10";Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"tabs",tabsBucket),_activateTerminal(instance.id),panel.isVisible()||panel.show();try{instance.fitAddon.fit()}catch(e){}await instance.spawn()}function _activateTerminal(id){activeTerminalId=id;for(const inst of terminalInstances)inst.id===id?inst.show():inst.hide();_updateFlyout(),_refreshAllProcesses()}async function _closeTerminal(id){const idx=terminalInstances.findIndex(t=>t.id===id);if(-1===idx)return;const instance=terminalInstances[idx];if(instance.isAlive)try{const result=await nodeConnector.execPeer("getTerminalProcess",{id:id}),processName=result.process||"";if(processName&&!_isShellProcess(processName)){const message=StringUtils.format(Strings.TERMINAL_CLOSE_CONFIRM_MSG,_escapeHtml(processName)),dialog=Dialogs.showConfirmDialog(Strings.TERMINAL_CLOSE_CONFIRM_TITLE,message),buttonId=await dialog.getPromise();if(buttonId!==Dialogs.DIALOG_BTN_OK)return}}catch(e){}if(instance.dispose(),terminalInstances.splice(idx,1),delete processInfo[id],Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"close","user"),activeTerminalId===id)if(terminalInstances.length>0){const newActive=terminalInstances[Math.min(idx,terminalInstances.length-1)];_activateTerminal(newActive.id)}else activeTerminalId=null;0===terminalInstances.length&&panel.hide(),_updateFlyout()}function _getActiveTerminal(){return terminalInstances.find(t=>t.id===activeTerminalId)||null}function _clearActiveTerminal(){const active=_getActiveTerminal();active&&active.clear()}function _killActiveTerminal(){const active=_getActiveTerminal();active&&active.isAlive&&nodeConnector.execPeer("killTerminal",{id:active.id}).catch(err=>{console.error("Terminal: kill error:",err)})}function _onTerminalTitleChanged(id){const instance=terminalInstances.find(t=>t.id===id);instance&&(instance._titleStale=!1),_updateFlyout(),_updateTabProcess(id)}function _updateTabProcess(id){const instance=terminalInstances.find(t=>t.id===id);instance&&instance.isAlive&&nodeConnector.execPeer("getTerminalProcess",{id:id}).then(function(result){const newProc=result.process||"";if(processInfo[id]!==newProc){const oldProc=processInfo[id];processInfo[id]=newProc,oldProc&&!_isShellProcess(oldProc)&&_isShellProcess(newProc)&&(instance._titleStale=!0),_updateFlyout()}}).catch(function(){})}function _refreshAllProcesses(){for(const inst of terminalInstances)inst.isAlive&&_updateTabProcess(inst.id)}function _extractCwdBasename(title){const colonIdx=title.indexOf(": "),pathPart=colonIdx>=0?title.slice(colonIdx+2):title,trimmed=pathPart.replace(/\/+$/,""),lastSlash=trimmed.lastIndexOf("/");return lastSlash>=0?trimmed.slice(lastSlash+1):trimmed}function _updateFlyout(){$flyoutList.empty();for(const inst of terminalInstances){const proc=processInfo[inst.id]||"",basename=proc?proc.split("/").pop().split("\\").pop():"",label=basename||"Terminal",displayTitle=inst._titleStale?inst.shellProfile.name:inst.title,cwdName=_extractCwdBasename(displayTitle),$item=$('<div class="terminal-flyout-item"></div>').attr("data-terminal-id",inst.id).attr("title",displayTitle).toggleClass("active",inst.id===activeTerminalId);inst.isAlive||$item.css("opacity","0.6"),$item.append('<span class="terminal-flyout-close"><i class="fa-solid fa-xmark"></i></span>'),$item.append('<span class="terminal-flyout-icon"><i class="fa-solid fa-terminal"></i></span>'),$item.append($('<span class="terminal-flyout-title"></span>').text(label)),cwdName&&$item.append($('<span class="terminal-flyout-cwd"></span>').text(cwdName)),$item.on("click",function(e){$(e.target).closest(".terminal-flyout-close").length||_activateTerminal(inst.id)}),$item.find(".terminal-flyout-close").on("click",function(e){e.stopPropagation(),_closeTerminal(inst.id)}),$flyoutList.append($item)}}function _onTerminalProcessExit(id,exitCode){delete processInfo[id],_updateFlyout(),Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL,"exit",0===exitCode?"ok":"err")}async function _showTerminal(options){if(options&&options.shellCommand){await _createNewTerminal();const active=_getActiveTerminal();return void(active&&active.isAlive&&(await active.firstDataReceived,nodeConnector.execPeer("writeTerminal",{id:active.id,data:options.shellCommand+"\r"})))}if(0===terminalInstances.length)return void await _createNewTerminal();const active=_getActiveTerminal(),terminalHasFocus=active&&active.$container&&active.$container[0].contains(document.activeElement);if(terminalInstances.length>=2&&panel.isVisible()&&terminalHasFocus){const activeIdx=terminalInstances.findIndex(t=>t.id===activeTerminalId),nextIdx=(activeIdx+1)%terminalInstances.length;_activateTerminal(terminalInstances[nextIdx].id)}else panel.show(),active&&(active.handleResize(),active.focus())}function _updateTabBarMode(){$panel.toggleClass("terminal-tabs-expanded",$panel.width()>=840)}function _handleResize(){_updateTabBarMode();const active=_getActiveTerminal();active&&active.handleResize(),_refreshAllProcesses()}function _setupPhoenixShortcuts(){const PASSTHROUGH_COMMANDS=[Commands.VIEW_TERMINAL,Commands.CMD_KEYBOARD_NAV_UI_OVERLAY];let passthroughShortcuts=new Set;function rebuild(){passthroughShortcuts=new Set;for(const cmdId of PASSTHROUGH_COMMANDS)for(const binding of KeyBindingManager.getKeyBindings(cmdId))binding.key&&passthroughShortcuts.add(binding.key)}rebuild(),KeyBindingManager.on(KeyBindingManager.EVENT_KEY_BINDING_ADDED,rebuild),KeyBindingManager.on(KeyBindingManager.EVENT_KEY_BINDING_REMOVED,rebuild),KeyBindingManager.addGlobalKeydownHook(function(event,shortcut){if("keydown"!==event.type)return!1;const el=document.activeElement;if(!el||!$contentArea[0].contains(el))return!1;const ctrlOrMeta=event.ctrlKey||event.metaKey,key=event.key.toLowerCase();return ctrlOrMeta&&!event.shiftKey&&"k"===key?(event.preventDefault(),_clearActiveTerminal(),!0):(ctrlOrMeta&&!event.shiftKey&&"l"===key&&_showClearBufferHintToast(),!shortcut||!passthroughShortcuts.has(shortcut))})}function _updateAllThemes(){for(const inst of terminalInstances)inst.updateTheme()}function _showFocusHintToast(){if(_focusToastShown)return;_focusToastShown=!0;const shortcutKey="<kbd>Shift+Esc</kbd>",message=StringUtils.format(Strings.TERMINAL_FOCUS_HINT,shortcutKey);NotificationUI.showToastOn($contentArea[0],message,{autoCloseTimeS:5,dismissOnClick:!0})}function _showClearBufferHintToast(){if(_clearHintShown)return;_clearHintShown=!0;const isMac="mac"===brackets.platform,shortcutKey=isMac?"<kbd>Cmd+K</kbd>":"<kbd>Ctrl+K</kbd>",message=StringUtils.format(Strings.TERMINAL_CLEAR_BUFFER_HINT,shortcutKey);NotificationUI.showToastOn($contentArea[0],message,{autoCloseTimeS:5,dismissOnClick:!0})}function _escapeHtml(str){const div=document.createElement("div");return div.textContent=str,div.innerHTML}function _disposeAll(){for(const inst of terminalInstances)inst.dispose();terminalInstances=[],processInfo={}}async function _disposeAllAsync(){const killPromises=terminalInstances.filter(function(inst){return inst.isAlive&&!inst._disposed}).map(function(inst){return nodeConnector.execPeer("killTerminal",{id:inst.id}).catch(function(){})});_disposeAll(),await Promise.all(killPromises)}CommandManager.register("New Terminal","terminal.new",_createNewTerminal,{supportsDesignMode:!0}),CommandManager.register(Strings.CMD_VIEW_TERMINAL,CMD_VIEW_TERMINAL,_showTerminal,{supportsDesignMode:!0}),CommandManager.register(Strings.CMD_OPEN_IN_INTEGRATED_TERMINAL,Commands.NAVIGATE_OPEN_IN_INTEGRATED_TERMINAL,function(){const entry=ProjectManager.getSelectedItem();let cwdPath;if(entry)cwdPath=entry.isDirectory?entry.fullPath:entry.parentPath;else{const projectRoot=ProjectManager.getProjectRoot();cwdPath=projectRoot?projectRoot.fullPath:void 0}_createNewTerminal(cwdPath)},{supportsDesignMode:!0}),CommandManager.register(Strings.CMD_COPY,"terminal.copy",function(){const active=_getActiveTerminal();active&&active.terminal.hasSelection()&&(navigator.clipboard.writeText(active.terminal.getSelection()),active.focus())}),CommandManager.register(Strings.CMD_PASTE,"terminal.paste",function(){const active=_getActiveTerminal();active&&active.isAlive&&(active.focus(),Phoenix.app.clipboardReadText().then(function(text){text&&nodeConnector.execPeer("writeTerminal",{id:active.id,data:text})}).catch(function(err){console.error("Terminal paste failed:",err)}))}),CommandManager.register(Strings.TERMINAL_CLEAR,"terminal.clear",function(){_clearActiveTerminal();const active=_getActiveTerminal();active&&active.focus()});const terminalContextMenu=Menus.registerContextMenu("terminal-context-menu");function _createToolbarButton(){const $btn=$("<a>").attr({id:"terminal-toolbar-button",href:"#",title:Strings.CMD_VIEW_TERMINAL}).insertBefore("#app-drawer-button");$btn.on("click",function(){if(Metrics.countEvent(Metrics.EVENT_TYPE.UI,"toolbarBtn","terminal"),WorkspaceManager.isInDesignMode())return CommandManager.execute(Commands.VIEW_TOGGLE_DESIGN_MODE),void CommandManager.execute(CMD_VIEW_TERMINAL);panel&&panel.isVisible()?panel.hide():CommandManager.execute(CMD_VIEW_TERMINAL)});const PanelView=require("view/PanelView");PanelView.on(PanelView.EVENT_PANEL_SHOWN,function(_event,panelId){$btn.toggleClass("selected-button",panelId===PANEL_ID)}),PanelView.on(PanelView.EVENT_PANEL_HIDDEN,function(_event,panelId){panelId!==PANEL_ID&&panelId!==WorkspaceManager.DEFAULT_PANEL_ID||$btn.removeClass("selected-button")})}terminalContextMenu.addMenuItem("terminal.copy"),terminalContextMenu.addMenuItem("terminal.paste"),terminalContextMenu.addMenuDivider(),terminalContextMenu.addMenuItem("terminal.clear"),terminalContextMenu.on(Menus.EVENT_BEFORE_CONTEXT_MENU_OPEN,function(){const active=_getActiveTerminal(),hasSelection=active&&active.terminal.hasSelection();CommandManager.get("terminal.copy").setEnabled(hasSelection),CommandManager.get("terminal.paste").setEnabled(active&&active.isAlive),CommandManager.get("terminal.clear").setEnabled(!!active)}),AppInit.appReady(function(){Phoenix.isSpecRunnerWindow||(_initNodeConnector(),_createPanel(),_createToolbarButton(),panel.registerOnCloseRequestedHandler(async function(){const aliveInstances=terminalInstances.filter(inst=>inst.isAlive),results=await Promise.all(aliveInstances.map(function(inst){return nodeConnector.execPeer("getTerminalProcess",{id:inst.id}).catch(function(){return{process:""}})})),activeProcesses=[];for(const result of results)result.process&&!_isShellProcess(result.process)&&activeProcesses.push(result.process);let title,message,confirmText;const count=terminalInstances.length,procCount=activeProcesses.length;if(1===count&&procCount>0)title=Strings.TERMINAL_CLOSE_SINGLE_TITLE,message=Strings.TERMINAL_CLOSE_SINGLE_MSG,confirmText=Strings.TERMINAL_CLOSE_SINGLE_BTN;else if(count>1&&0===procCount)title=Strings.TERMINAL_CLOSE_ALL_TITLE,message=Strings.TERMINAL_CLOSE_ALL_MSG,confirmText=Strings.TERMINAL_CLOSE_ALL_BTN;else{if(!(count>1&&procCount>0))return await _disposeAllAsync(),activeTerminalId=null,_updateFlyout(),!0;title=Strings.TERMINAL_CLOSE_ALL_TITLE,message=1===procCount?Strings.TERMINAL_CLOSE_ALL_MSG_PROCESS_ONE:StringUtils.format(Strings.TERMINAL_CLOSE_ALL_MSG_PROCESS_MANY,procCount),confirmText=Strings.TERMINAL_CLOSE_ALL_STOP_BTN}const buttons=[{className:Dialogs.DIALOG_BTN_CLASS_NORMAL,id:Dialogs.DIALOG_BTN_CANCEL,text:Strings.CANCEL},{className:Dialogs.DIALOG_BTN_CLASS_PRIMARY,id:Dialogs.DIALOG_BTN_OK,text:confirmText}],dialog=Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO,title,message,buttons),buttonId=await dialog.getPromise();return buttonId===Dialogs.DIALOG_BTN_OK&&(await _disposeAllAsync(),activeTerminalId=null,_updateFlyout(),!0)}),ShellProfiles.init(nodeConnector).then(function(){const shells=ShellProfiles.getShells(),systemDefault=ShellProfiles.getDefaultShell();originalDefaultShellName=systemDefault?systemDefault.name:null,shells.length<=1&&$panel.find(".terminal-flyout-dropdown-btn").addClass("forced-hidden"),_populateShellDropdown(),_updateNewTerminalButtonLabel()}),window.addEventListener("beforeunload",_disposeAll))}),exports.CMD_VIEW_TERMINAL=CMD_VIEW_TERMINAL,exports.CMD_NEW_TERMINAL="terminal.new",exports.getNodeConnector=function(){return nodeConnector},Phoenix.isTestWindow&&(exports._getActiveTerminal=_getActiveTerminal,exports._refreshAllProcesses=_refreshAllProcesses,exports._writeToActiveTerminal=function(data){const active=_getActiveTerminal();return active&&active.isAlive?nodeConnector.execPeer("writeTerminal",{id:active.id,data:data}):Promise.reject(new Error("No active terminal"))},exports._disposeAll=async function(){await _disposeAllAsync(),activeTerminalId=null})});