{-# LANGUAGE OverloadedStrings #-} module Commands ( runCommand , runJump , runQuick , runConfigCmd , printShellIntegration ) where import Control.Monad (forM_, unless) import Data.List (intercalate) import qualified Data.Map as M import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import System.Directory (doesDirectoryExist) import System.Exit (exitFailure) import System.Info (os) import System.IO (hFlush, stdout) import System.Process (spawnCommand, waitForProcess) import Config import Types import Utils -- | 运行主命令 runCommand :: Command -> IO () runCommand cmd = case cmd of Jump name quiet -> runJump name quiet JumpInteractive quiet -> runJumpInteractive quiet Quick action quiet -> runQuick action quiet ConfigCmd action quiet -> runConfigCmd action quiet ShellIntegration -> printShellIntegration Version -> putStrLn "quickjump version 0.1.0.0" -- | 跳转到指定条目 runJump :: Text -> Bool -> IO () runJump name quiet = do cfg <- ensureConfigExists case findEntry name cfg of Nothing -> do unless quiet $ do putStrLn $ "Unknown jump target: " ++ T.unpack name putStrLn "Use 'quickjump config list' to see available targets" exitFailure Just entry -> do expanded <- expandPath (path entry) exists <- doesDirectoryExist expanded if exists then do -- 输出 cd 命令供 shell 执行 -- 使用引号包裹路径以处理包含空格的路径 let cdCmd = case os of "mingw32" -> "cd \"" ++ expanded ++ "\"" "mingw64" -> "cd \"" ++ expanded ++ "\"" _ -> "cd " ++ show expanded putStrLn cdCmd else do unless quiet $ putStrLn $ "Directory does not exist: " ++ expanded exitFailure -- | 交互式选择跳转 runJumpInteractive :: Bool -> IO () runJumpInteractive quiet = do cfg <- ensureConfigExists let sorted = getSortedEntries cfg if null sorted then unless quiet $ do putStrLn "No jump targets configured" putStrLn "Use 'quickjump config add ' to add one" else do unless quiet $ do putStrLn "Available jump targets:" putStrLn "" forM_ (zip [1..] sorted) $ \(i, (name, entry)) -> do let desc = fromMaybe "" (description entry) putStrLn $ " " ++ show (i :: Int) ++ ". " ++ T.unpack name ++ " -> " ++ path entry ++ if T.null desc then "" else " (" ++ T.unpack desc ++ ")" putStr "\nSelect target (number or name): " hFlush stdout selection <- getLine case reads selection of [(n, "")] | n > 0 && n <= length sorted -> runJump (fst $ sorted !! (n - 1)) quiet _ -> runJump (T.pack selection) quiet -- | 运行快速操作 runQuick :: QuickAction -> Bool -> IO () runQuick action quiet = do cfg <- ensureConfigExists case action of QuickOpen name -> do case findEntry name cfg of Nothing -> do unless quiet $ putStrLn $ "Unknown quick target: " ++ T.unpack name exitFailure Just entry -> openPath (path entry) cfg quiet QuickOpenPath p -> openPath p cfg quiet QuickList -> do let sorted = getSortedEntries cfg unless quiet $ do putStrLn "Quick access targets:" putStrLn "" forM_ sorted $ \(name, entry) -> do let desc = fromMaybe "" (description entry) putStrLn $ " " ++ padRight 15 (T.unpack name) ++ " -> " ++ padRight 30 (path entry) ++ if T.null desc then "" else " # " ++ T.unpack desc QuickDefault -> case defaultPath cfg of Nothing -> do unless quiet $ do putStrLn "No default path configured" putStrLn "Use 'quickjump config set-default ' to set one" exitFailure Just p -> openPath p cfg quiet -- | 打开路径(使用文件管理器或 cd) openPath :: FilePath -> Config -> Bool -> IO () openPath p cfg quiet = do expanded <- expandPath p exists <- doesDirectoryExist expanded unless exists $ do unless quiet $ putStrLn $ "Directory does not exist: " ++ expanded exitFailure -- 尝试使用配置的文件管理器,或者自动检测 let fm = fileManager cfg case fm of Just cmd -> runFileManager cmd expanded Nothing -> autoDetectAndOpen expanded quiet -- | 自动检测并打开文件管理器 autoDetectAndOpen :: FilePath -> Bool -> IO () autoDetectAndOpen targetPath quiet = do let (cmd, args) = case os of "darwin" -> ("open", [targetPath]) "mingw32" -> ("explorer", [targetPath]) "mingw64" -> ("explorer", [targetPath]) "cygwin" -> ("cygstart", [targetPath]) _ -> ("xdg-open", [targetPath]) -- Linux and others -- 检查命令是否存在 exists <- commandExists cmd if exists then do _ <- spawnCommand (unwords (cmd : map show args)) >>= waitForProcess return () else do unless quiet $ do putStrLn $ "Cannot open file manager. Please configure one:" putStrLn $ " quickjump config set-file-manager " -- 输出 cd 命令作为备选 putStrLn $ "cd " ++ show targetPath -- | 运行文件管理器 runFileManager :: FilePath -> FilePath -> IO () runFileManager cmd targetPath = do expanded <- expandPath targetPath let fullCmd = cmd ++ " " ++ show expanded _ <- spawnCommand fullCmd >>= waitForProcess return () -- | 运行配置命令 runConfigCmd :: ConfigAction -> Bool -> IO () runConfigCmd action quiet = do cfg <- ensureConfigExists case action of ConfigAdd name targetPath mDesc -> do expanded <- expandPath targetPath exists <- doesDirectoryExist expanded unless exists $ do unless quiet $ putStrLn $ "Warning: Directory does not exist: " ++ expanded let entry = JumpEntry { path = targetPath , description = mDesc , priority = 100 } newCfg = cfg { entries = M.insert name entry (entries cfg) } saveConfig newCfg unless quiet $ putStrLn $ "Added '" ++ T.unpack name ++ "' -> " ++ targetPath ConfigRemove name -> do if M.member name (entries cfg) then do let newCfg = cfg { entries = M.delete name (entries cfg) } saveConfig newCfg unless quiet $ putStrLn $ "Removed '" ++ T.unpack name ++ "'" else do unless quiet $ putStrLn $ "No such entry: '" ++ T.unpack name ++ "'" exitFailure ConfigList -> do let sorted = getSortedEntries cfg unless quiet $ do if null sorted then putStrLn "No entries configured" else do putStrLn "Configured jump entries:" putStrLn "" forM_ sorted $ \(name, entry) -> do let desc = fromMaybe "" (description entry) putStrLn $ " " ++ padRight 15 (T.unpack name) ++ " -> " ++ padRight 30 (path entry) ++ if T.null desc then "" else " # " ++ T.unpack desc ConfigSetDefault targetPath -> do expanded <- expandPath targetPath exists <- doesDirectoryExist expanded unless exists $ do unless quiet $ putStrLn $ "Warning: Directory does not exist: " ++ expanded let newCfg = cfg { defaultPath = Just targetPath } saveConfig newCfg unless quiet $ putStrLn $ "Set default path to: " ++ targetPath ConfigSetEditor cmd -> do let newCfg = cfg { editor = Just cmd } saveConfig newCfg unless quiet $ putStrLn $ "Set editor to: " ++ cmd ConfigSetFileManager cmd -> do let newCfg = cfg { fileManager = Just cmd } saveConfig newCfg unless quiet $ putStrLn $ "Set file manager to: " ++ cmd ConfigExport targetPath -> do saveConfigTo targetPath cfg unless quiet $ putStrLn $ "Config exported to: " ++ targetPath ConfigImport targetPath merge -> do imported <- loadConfigFrom targetPath let merged = mergeConfigs cfg imported merge saveConfig merged unless quiet $ if merge then putStrLn "Config imported and merged successfully" else putStrLn "Config imported (replaced existing)" ConfigEdit -> do let ed = fromMaybe (defaultEditor os) (editor cfg) configPath <- getConfigPath _ <- spawnCommand (ed ++ " " ++ show configPath) >>= waitForProcess return () ConfigShow -> do configPath <- getConfigPath unless quiet $ do putStrLn $ "Config location: " ++ configPath putStrLn $ "Version: " ++ T.unpack (version cfg) putStrLn $ "Entries: " ++ show (M.size $ entries cfg) putStrLn $ "Default path: " ++ fromMaybe "(not set)" (defaultPath cfg) putStrLn $ "Editor: " ++ fromMaybe "(not set)" (editor cfg) putStrLn $ "File manager: " ++ fromMaybe "(auto-detect)" (fileManager cfg) -- | 获取默认编辑器 defaultEditor :: String -> String defaultEditor platform = case platform of "darwin" -> "open -t" "mingw32" -> "notepad" "mingw64" -> "notepad" _ -> "vi" -- | 打印 shell 集成脚本 printShellIntegration :: IO () printShellIntegration = do putStrLn $ shellScript os -- | 获取对应 shell 的集成脚本 shellScript :: String -> String shellScript platform = case platform of "mingw32" -> windowsPowerShellScript "mingw64" -> windowsPowerShellScript "cygwin" -> bashScript "darwin" -> bashScript _ -> bashScript -- | Bash/Zsh 集成脚本 bashScript :: String bashScript = intercalate "\n" [ "# QuickJump Shell Integration for Bash/Zsh" , "# Add this to your shell profile (.bashrc, .zshrc, etc.)" , "#" , "# eval \"$(quickjump shell-integration)\"" , "" , "# Bash/Zsh function for directory jumping" , "qj() {" , " local output=$(quickjump jump \"$1\")" , " if [[ $output == cd* ]]; then" , " eval \"$output\"" , " else" , " echo \"$output\"" , " fi" , "}" , "" , "# Quick open function" , "qo() {" , " quickjump quick \"$1\"" , "}" , "" , "# Quiet mode function" , "qjq() {" , " quickjump --quiet jump \"$1\"" , "}" , "" , "# Tab completion for bash" , "if [ -n \"$BASH_VERSION\" ]; then" , " _qj_complete() {" , " local cur=\"${COMP_WORDS[COMP_CWORD]}\"" , " local entries=$(quickjump config list 2>/dev/null | grep '^ ' | awk '{print $1}')" , " COMPREPLY=($(compgen -W \"$entries\" -- \"$cur\"))" , " }" , " complete -F _qj_complete qj" , " complete -F _qj_complete qjq" , "fi" , "" , "# Tab completion for zsh" , "if [ -n \"$ZSH_VERSION\" ]; then" , " _qj_complete() {" , " local -a entries" , " entries=(${(f)\"$(quickjump config list 2>/dev/null | grep '^ ' | awk '{print $1}')\"})" , " _describe 'jump targets' entries" , " }" , " compdef _qj_complete qj" , " compdef _qj_complete qjq" , "fi" ] -- | Windows PowerShell 集成脚本 windowsPowerShellScript :: String windowsPowerShellScript = intercalate "\n" [ "# QuickJump Shell Integration for PowerShell" , "# Add this to your PowerShell profile ($PROFILE)" , "#" , "# To edit your profile: notepad $PROFILE" , "#" , "# . (quickjump shell-integration | Out-String)" , "" , "# Function for directory jumping" , "function qj {" , " param([string]$name)" , " $output = quickjump jump $name" , " if ($output -like 'cd *') {" , " # Remove 'cd ' prefix and execute" , " $path = $output -replace '^cd \"?([^\"\"]*)\"?$', '$1'" , " Set-Location $path" , " } else {" , " Write-Output $output" , " }" , "}" , "" , "# Quiet mode function" , "function qjq {" , " param([string]$name)" , " $output = quickjump --quiet jump $name" , " if ($output -like 'cd *') {" , " $path = $output -replace '^cd \"?([^\"\"]*)\"?$', '$1'" , " Set-Location $path" , " }" , "}" , "" , "# Quick open function" , "function qo {" , " param([string]$name)" , " quickjump quick $name" , "}" , "" , "# Tab completion" , "Register-ArgumentCompleter -CommandName qj -ScriptBlock {" , " param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)" , " $entries = quickjump config list 2>$null | Select-String '^ ' | ForEach-Object {" , " $_.ToString().Trim().Split()[0]" , " }" , " $entries | Where-Object { $_ -like \"$wordToComplete*\" } | ForEach-Object {" , " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)" , " }" , "}" , "" , "Register-ArgumentCompleter -CommandName qjq -ScriptBlock {" , " param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)" , " $entries = quickjump config list 2>$null | Select-String '^ ' | ForEach-Object {" , " $_.ToString().Trim().Split()[0]" , " }" , " $entries | Where-Object { $_ -like \"$wordToComplete*\" } | ForEach-Object {" , " [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)" , " }" , "}" ]