diff --git a/src/components/terminal/terminal.js b/src/components/terminal/terminal.js index 942039812..39ede08c1 100644 --- a/src/components/terminal/terminal.js +++ b/src/components/terminal/terminal.js @@ -120,6 +120,58 @@ export default class TerminalComponent { // Handle copy/paste keybindings this.setupCopyPasteHandlers(); + + // Handle custom OSC 7777 for acode CLI commands + this.setupOscHandler(); + } + + /** + * Setup custom OSC handler for acode CLI integration + * OSC 7777 format: \e]7777;command;arg1;arg2;...\a + */ + setupOscHandler() { + // Register custom OSC handler for ID 7777 + // Format: command;arg1;arg2;... where arg2 (path) may contain semicolons + this.terminal.parser.registerOscHandler(7777, (data) => { + const firstSemi = data.indexOf(";"); + if (firstSemi === -1) { + console.warn("Invalid OSC 7777 format:", data); + return true; + } + + const command = data.substring(0, firstSemi); + const rest = data.substring(firstSemi + 1); + + switch (command) { + case "open": { + // Format: open;type;path (path may contain semicolons) + const secondSemi = rest.indexOf(";"); + if (secondSemi === -1) { + console.warn("Invalid OSC 7777 open format:", data); + return true; + } + const type = rest.substring(0, secondSemi); + const path = rest.substring(secondSemi + 1); + this.handleOscOpen(type, path); + break; + } + default: + console.warn("Unknown OSC 7777 command:", command); + } + return true; + }); + } + + /** + * Handle OSC open command from acode CLI + * @param {string} type - "file" or "folder" + * @param {string} path - Path to open + */ + handleOscOpen(type, path) { + if (!path) return; + + // Emit event for the app to handle + this.onOscOpen?.(type, path); } /** diff --git a/src/components/terminal/terminalManager.js b/src/components/terminal/terminalManager.js index 816a1c14e..d2ca901d5 100644 --- a/src/components/terminal/terminalManager.js +++ b/src/components/terminal/terminalManager.js @@ -9,6 +9,8 @@ import "@xterm/xterm/css/xterm.css"; import quickTools from "components/quickTools"; import toast from "components/toast"; import confirm from "dialogs/confirm"; +import openFile from "lib/openFile"; +import openFolder from "lib/openFolder"; import appSettings from "lib/settings"; import helpers from "utils/helpers"; @@ -577,6 +579,30 @@ class TerminalManager { toast(message); }; + // Handle acode CLI open commands (OSC 7777) + terminalComponent.onOscOpen = async (type, path) => { + if (!path) return; + + // Convert proot path + const fileUri = this.convertProotPath(path); + // Extract folder/file name from path + const name = path.split("/").filter(Boolean).pop() || "folder"; + + try { + if (type === "folder") { + // Open folder in sidebar + await openFolder(fileUri, { name, saveState: true, listFiles: true }); + toast(`Opened folder: ${name}`); + } else { + // Open file in editor + await openFile(fileUri, { render: true }); + } + } catch (error) { + console.error("Failed to open from terminal:", error); + toast(`Failed to open: ${path}`); + } + }; + // Store references for cleanup terminalFile._terminalId = terminalId; terminalFile.terminalComponent = terminalComponent; @@ -791,6 +817,40 @@ class TerminalManager { }); } + /** + * Convert proot internal path to app-accessible path + * @param {string} prootPath - Path from inside proot environment + * @returns {string} App filesystem path + */ + convertProotPath(prootPath) { + if (!prootPath) return prootPath; + + const packageName = window.BuildInfo?.packageName || "com.foxdebug.acode"; + const dataDir = `/data/user/0/${packageName}`; + const alpineRoot = `${dataDir}/files/alpine`; + + let convertedPath; + + if (prootPath.startsWith("/public")) { + // /public -> /data/user/0/com.foxdebug.acode/files/public + convertedPath = `file://${dataDir}/files${prootPath}`; + } else if ( + prootPath.startsWith("/sdcard") || + prootPath.startsWith("/storage") || + prootPath.startsWith("/data") + ) { + convertedPath = `file://${prootPath}`; + } else if (prootPath.startsWith("/")) { + // Everything else is relative to alpine root + convertedPath = `file://${alpineRoot}${prootPath}`; + } else { + convertedPath = prootPath; + } + + //console.log(`Path conversion: ${prootPath} -> ${convertedPath}`); + return convertedPath; + } + shouldConfirmTerminalClose() { const settings = appSettings?.value?.terminalSettings; if (settings && settings.confirmTabClose === false) { diff --git a/src/plugins/terminal/scripts/init-alpine.sh b/src/plugins/terminal/scripts/init-alpine.sh index 1f051f06c..eee2e1c8b 100644 --- a/src/plugins/terminal/scripts/init-alpine.sh +++ b/src/plugins/terminal/scripts/init-alpine.sh @@ -65,6 +65,75 @@ Working with packages: EOF fi + # Create acode CLI tool + if [ ! -e "$PREFIX/alpine/usr/local/bin/acode" ]; then + mkdir -p "$PREFIX/alpine/usr/local/bin" + cat <<'ACODE_CLI' > "$PREFIX/alpine/usr/local/bin/acode" +#!/bin/bash +# acode - Open files/folders in Acode editor +# Uses OSC escape sequences to communicate with the Acode terminal + +usage() { + echo "Usage: acode [file/folder...]" + echo "" + echo "Open files or folders in Acode editor." + echo "" + echo "Examples:" + echo " acode file.txt # Open a file" + echo " acode . # Open current folder" + echo " acode ~/project # Open a folder" + echo " acode -h, --help # Show this help" +} + +get_abs_path() { + local path="$1" + local abs_path + abs_path=$(realpath -- "$path" 2>/dev/null) + if [[ $? -ne 0 ]]; then + if [[ "$path" == /* ]]; then + abs_path="$path" + else + abs_path="$PWD/$path" + fi + fi + echo "$abs_path" +} + +open_in_acode() { + local path=$(get_abs_path "$1") + local type="file" + [[ -d "$path" ]] && type="folder" + + # Send OSC 7777 escape sequence: \e]7777;cmd;type;path\a + # The terminal component will intercept and handle this + printf '\e]7777;open;%s;%s\a' "$type" "$path" +} + +if [[ $# -eq 0 ]]; then + open_in_acode "." + exit 0 +fi + +for arg in "$@"; do + case "$arg" in + -h|--help) + usage + exit 0 + ;; + *) + if [[ -e "$arg" ]]; then + open_in_acode "$arg" + else + echo "Error: '$arg' does not exist" >&2 + exit 1 + fi + ;; + esac +done +ACODE_CLI + chmod +x "$PREFIX/alpine/usr/local/bin/acode" + fi + # Create initrc if it doesn't exist #initrc runs in bash so we can use bash features if [ ! -e "$PREFIX/alpine/initrc" ]; then