✨ feat:初始化 QuickJump 项目,实现完整的目录跳转和配置管理功能
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dist-newstyle
|
||||||
33
CHANGELOG.md
Normal file
33
CHANGELOG.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Revision history for quickjump
|
||||||
|
|
||||||
|
## 0.3.0.1 -- 2026-02-04
|
||||||
|
|
||||||
|
* 添加静默模式选项 (--quiet/-q),用于抑制输出消息
|
||||||
|
* 改进 Windows 路径处理,支持带空格的路径
|
||||||
|
* 优化配置文件管理,支持 XDG 配置目录
|
||||||
|
* 增强错误处理和用户提示信息
|
||||||
|
|
||||||
|
## 0.3.0.0 -- 2026-02-03
|
||||||
|
|
||||||
|
* 新增快速操作命令 (quick/k),支持快速打开目录
|
||||||
|
* 添加配置导出/导入功能,支持配置备份和迁移
|
||||||
|
* 新增配置编辑功能,可直接使用编辑器打开配置文件
|
||||||
|
* 添加默认路径设置,可配置默认打开的目录
|
||||||
|
* 支持设置首选编辑器和文件管理器
|
||||||
|
* 改进交互式选择模式,支持按编号或名称选择
|
||||||
|
* 增强路径展开功能,支持环境变量和 ~ 符号
|
||||||
|
|
||||||
|
## 0.2.0.0 -- 2026-02-02
|
||||||
|
|
||||||
|
* 实现完整的目录跳转功能 (jump/j 命令)
|
||||||
|
* 添加配置管理系统,支持添加、删除、列出跳转条目
|
||||||
|
* 新增交互式选择模式 (--interactive/-i)
|
||||||
|
* 实现配置文件持久化,使用 JSON 格式存储
|
||||||
|
* 添加条目描述和优先级支持
|
||||||
|
* 实现自动检测文件管理器功能,支持跨平台
|
||||||
|
* 添加 Shell 集成功能,支持 Bash/Zsh 和 PowerShell
|
||||||
|
* 实现 Tab 补全功能
|
||||||
|
|
||||||
|
## 0.1.0.0 -- 2026-02-02
|
||||||
|
|
||||||
|
* First version. Released on an unsuspecting world.
|
||||||
20
LICENSE
Normal file
20
LICENSE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2026 Sidney Zhang
|
||||||
|
|
||||||
|
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.
|
||||||
336
README.md
Normal file
336
README.md
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# QuickJump
|
||||||
|
|
||||||
|
一个使用 Haskell 编写的快速目录跳转命令行工具,支持配置管理、快速打开和导入导出功能。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- **快速目录跳转** - 根据配置快速跳转到常用目录
|
||||||
|
- **快速打开** - 使用文件管理器快速打开文件夹
|
||||||
|
- **配置管理** - 添加、删除、修改配置条目
|
||||||
|
- **导入导出** - 备份和分享配置
|
||||||
|
- **Shell 集成** - 提供 Bash/Zsh/PowerShell 补全支持
|
||||||
|
- **交互式选择** - 支持交互式选择跳转目标
|
||||||
|
- **静默模式** - 抑制输出消息,适合脚本使用
|
||||||
|
- **跨平台支持** - 支持 Windows、macOS 和 Linux
|
||||||
|
|
||||||
|
## 版本
|
||||||
|
|
||||||
|
当前版本: **0.3.0.1**
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 使用 Cabal
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cabal build
|
||||||
|
cabal install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 Stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stack build
|
||||||
|
stack install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置 Shell 集成
|
||||||
|
|
||||||
|
### Linux/macOS (Bash/Zsh)
|
||||||
|
|
||||||
|
将以下命令添加到你的 `.bashrc` 或 `.zshrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
eval "$(quickjump shell-integration)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows (PowerShell)
|
||||||
|
|
||||||
|
将以下命令添加到你的 PowerShell 配置文件中:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 首先查看你的配置文件路径
|
||||||
|
echo $PROFILE
|
||||||
|
|
||||||
|
# 如果文件不存在,创建它
|
||||||
|
if (!(Test-Path $PROFILE)) { New-Item -Type File -Path $PROFILE -Force }
|
||||||
|
|
||||||
|
# 编辑配置文件
|
||||||
|
notepad $PROFILE
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在配置文件中添加:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
. (quickjump shell-integration | Out-String)
|
||||||
|
```
|
||||||
|
|
||||||
|
这将启用:
|
||||||
|
- `qj` 命令用于目录跳转
|
||||||
|
- `qo` 命令用于快速打开
|
||||||
|
- Tab 自动补全(PowerShell 也支持)
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 目录跳转
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 跳转到配置中的条目
|
||||||
|
quickjump jump home
|
||||||
|
quickjump j home
|
||||||
|
|
||||||
|
# 使用 Shell 集成函数(需要先配置 Shell 集成)
|
||||||
|
qj home
|
||||||
|
|
||||||
|
# 交互式选择跳转目标
|
||||||
|
quickjump jump --interactive
|
||||||
|
quickjump j -i
|
||||||
|
|
||||||
|
# 静默模式(不输出消息)
|
||||||
|
quickjump jump home --quiet
|
||||||
|
quickjump j home -q
|
||||||
|
```
|
||||||
|
|
||||||
|
### 快速打开
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 打开配置中的目录(使用文件管理器)
|
||||||
|
quickjump quick open home
|
||||||
|
quickjump k open home
|
||||||
|
|
||||||
|
# 使用 Shell 集成函数
|
||||||
|
qo home
|
||||||
|
|
||||||
|
# 打开指定路径
|
||||||
|
quickjump quick --path ~/Documents
|
||||||
|
quickjump k -p ~/Documents
|
||||||
|
|
||||||
|
# 打开默认目录
|
||||||
|
quickjump quick default
|
||||||
|
quickjump k -d
|
||||||
|
|
||||||
|
# 列出所有快速目标
|
||||||
|
quickjump quick list
|
||||||
|
quickjump k -l
|
||||||
|
|
||||||
|
# 静默模式
|
||||||
|
quickjump quick open home --quiet
|
||||||
|
quickjump k open home -q
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置管理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 添加新条目
|
||||||
|
quickjump config add myproject ~/Projects/myproject
|
||||||
|
quickjump config add work ~/Work --description "Work directory"
|
||||||
|
quickjump c add myproject ~/Projects/myproject
|
||||||
|
|
||||||
|
# 列出所有条目
|
||||||
|
quickjump config list
|
||||||
|
quickjump config ls
|
||||||
|
quickjump c list
|
||||||
|
|
||||||
|
# 删除条目
|
||||||
|
quickjump config remove myproject
|
||||||
|
quickjump config rm myproject
|
||||||
|
quickjump c remove myproject
|
||||||
|
|
||||||
|
# 设置默认路径
|
||||||
|
quickjump config set-default ~/Documents
|
||||||
|
quickjump c set-default ~/Documents
|
||||||
|
|
||||||
|
# 设置首选编辑器
|
||||||
|
quickjump config set-editor vim
|
||||||
|
quickjump config set-editor "code --wait"
|
||||||
|
quickjump c set-editor vim
|
||||||
|
|
||||||
|
# 设置首选文件管理器
|
||||||
|
quickjump config set-file-manager nautilus # Linux
|
||||||
|
quickjump config set-file-manager finder # macOS
|
||||||
|
quickjump config set-file-manager explorer # Windows
|
||||||
|
quickjump c set-file-manager explorer
|
||||||
|
|
||||||
|
# 编辑配置文件
|
||||||
|
quickjump config edit
|
||||||
|
quickjump c edit
|
||||||
|
|
||||||
|
# 显示当前配置
|
||||||
|
quickjump config show
|
||||||
|
quickjump c show
|
||||||
|
|
||||||
|
# 静默模式
|
||||||
|
quickjump config add myproject ~/Projects/myproject --quiet
|
||||||
|
quickjump c add myproject ~/Projects/myproject -q
|
||||||
|
```
|
||||||
|
|
||||||
|
### 导入导出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 导出配置
|
||||||
|
quickjump config export ~/backup/quickjump-config.json
|
||||||
|
quickjump c export ~/backup/quickjump-config.json
|
||||||
|
|
||||||
|
# 导入配置(替换现有)
|
||||||
|
quickjump config import ~/backup/quickjump-config.json
|
||||||
|
quickjump c import ~/backup/quickjump-config.json
|
||||||
|
|
||||||
|
# 导入配置(合并)
|
||||||
|
quickjump config import ~/backup/quickjump-config.json --merge
|
||||||
|
quickjump config import ~/backup/quickjump-config.json -m
|
||||||
|
quickjump c import ~/backup/quickjump-config.json -m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显示版本信息
|
||||||
|
quickjump --version
|
||||||
|
quickjump -v
|
||||||
|
|
||||||
|
# 显示帮助信息
|
||||||
|
quickjump --help
|
||||||
|
quickjump -h
|
||||||
|
|
||||||
|
# 显示特定命令的帮助
|
||||||
|
quickjump jump --help
|
||||||
|
quickjump config --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置文件
|
||||||
|
|
||||||
|
配置文件默认位于:
|
||||||
|
|
||||||
|
- **Linux/macOS**: `~/.config/quickjump/config.json`
|
||||||
|
- **Windows**: `%APPDATA%\quickjump\config.json`
|
||||||
|
|
||||||
|
可以通过环境变量 `QUICKJUMP_CONFIG` 自定义配置文件路径:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux/macOS
|
||||||
|
export QUICKJUMP_CONFIG=/path/to/custom/config.json
|
||||||
|
|
||||||
|
# Windows PowerShell
|
||||||
|
$env:QUICKJUMP_CONFIG = "C:\path\to\custom\config.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"entries": {
|
||||||
|
"home": {
|
||||||
|
"path": "~",
|
||||||
|
"description": "Home directory",
|
||||||
|
"priority": 1
|
||||||
|
},
|
||||||
|
"docs": {
|
||||||
|
"path": "~/Documents",
|
||||||
|
"description": "Documents folder",
|
||||||
|
"priority": 2
|
||||||
|
},
|
||||||
|
"downloads": {
|
||||||
|
"path": "~/Downloads",
|
||||||
|
"description": "Downloads folder",
|
||||||
|
"priority": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default_path": "~",
|
||||||
|
"editor": "vim",
|
||||||
|
"file_manager": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置字段说明
|
||||||
|
|
||||||
|
- `version`: 配置文件版本号
|
||||||
|
- `entries`: 跳转条目映射表
|
||||||
|
- `path`: 目标路径(支持 `~` 和环境变量)
|
||||||
|
- `description`: 可选描述信息
|
||||||
|
- `priority`: 优先级(数字越小越优先)
|
||||||
|
- `default_path`: 默认打开的路径
|
||||||
|
- `editor`: 首选编辑器命令
|
||||||
|
- `file_manager`: 首选文件管理器命令
|
||||||
|
|
||||||
|
## 命令速查表
|
||||||
|
|
||||||
|
### 主命令
|
||||||
|
|
||||||
|
| 命令 | 简写 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `quickjump jump <name>` | `quickjump j <name>` | 跳转到指定目录 |
|
||||||
|
| `quickjump jump --interactive` | `quickjump j -i` | 交互式选择跳转 |
|
||||||
|
| `quickjump quick open <name>` | `quickjump k open <name>` | 快速打开目录 |
|
||||||
|
| `quickjump quick --path <path>` | `quickjump k -p <path>` | 打开指定路径 |
|
||||||
|
| `quickjump quick default` | `quickjump k -d` | 打开默认目录 |
|
||||||
|
| `quickjump quick list` | `quickjump k -l` | 列出快速目标 |
|
||||||
|
| `quickjump config <action>` | `quickjump c <action>` | 配置管理 |
|
||||||
|
| `quickjump shell-integration` | - | 输出 Shell 集成脚本 |
|
||||||
|
|
||||||
|
### 全局选项
|
||||||
|
|
||||||
|
| 选项 | 简写 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `--version` | `-v` | 显示版本信息 |
|
||||||
|
| `--help` | `-h` | 显示帮助信息 |
|
||||||
|
| `--quiet` | `-q` | 静默模式(抑制输出) |
|
||||||
|
|
||||||
|
### 配置子命令
|
||||||
|
|
||||||
|
| 命令 | 简写 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `quickjump config add <name> <path>` | - | 添加条目 |
|
||||||
|
| `quickjump config remove <name>` | `quickjump config rm <name>` | 删除条目 |
|
||||||
|
| `quickjump config list` | `quickjump config ls` | 列出所有条目 |
|
||||||
|
| `quickjump config set-default <path>` | - | 设置默认路径 |
|
||||||
|
| `quickjump config set-editor <cmd>` | - | 设置编辑器 |
|
||||||
|
| `quickjump config set-file-manager <cmd>` | - | 设置文件管理器 |
|
||||||
|
| `quickjump config export <file>` | - | 导出配置 |
|
||||||
|
| `quickjump config import <file>` | - | 导入配置 |
|
||||||
|
| `quickjump config import <file> --merge` | `quickjump config import <file> -m` | 合并导入配置 |
|
||||||
|
| `quickjump config edit` | - | 编辑配置文件 |
|
||||||
|
| `quickjump config show` | - | 显示当前配置 |
|
||||||
|
|
||||||
|
### Shell 集成函数
|
||||||
|
|
||||||
|
| 函数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `qj <name>` | 跳转到指定目录 |
|
||||||
|
| `qjq <name>` | 静默模式跳转 |
|
||||||
|
| `qo <name>` | 快速打开目录 |
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
quickjump/
|
||||||
|
├── quickjump.cabal # Cabal 配置文件
|
||||||
|
├── CHANGELOG.md # 版本变更记录
|
||||||
|
├── README.md # 说明文档
|
||||||
|
└── app/
|
||||||
|
├── Main.hs # 程序入口和 CLI 解析
|
||||||
|
├── Types.hs # 数据类型定义
|
||||||
|
├── Config.hs # 配置管理
|
||||||
|
├── Commands.hs # 命令实现
|
||||||
|
└── Utils.hs # 工具函数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
- base >= 4.18
|
||||||
|
- aeson >= 2.2
|
||||||
|
- aeson-pretty >= 0.8
|
||||||
|
- optparse-applicative >= 0.18
|
||||||
|
- directory >= 1.3
|
||||||
|
- filepath >= 1.4
|
||||||
|
- process >= 1.6
|
||||||
|
- text >= 2.0
|
||||||
|
- bytestring >= 0.11
|
||||||
|
- containers >= 0.6
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
查看 [CHANGELOG.md](CHANGELOG.md) 了解详细的版本更新历史。
|
||||||
395
app/Commands.hs
Normal file
395
app/Commands.hs
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Commands
|
||||||
|
( runCommand
|
||||||
|
, runJump
|
||||||
|
, runQuick
|
||||||
|
, runConfigCmd
|
||||||
|
, printShellIntegration
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Monad (forM_, unless, when)
|
||||||
|
import Data.List (intercalate)
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Data.Maybe (fromMaybe, isJust)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import System.Directory (doesDirectoryExist, doesFileExist)
|
||||||
|
import System.Environment (lookupEnv)
|
||||||
|
import System.Exit (exitFailure, exitSuccess)
|
||||||
|
import System.FilePath ((</>))
|
||||||
|
import System.Info (os)
|
||||||
|
import System.IO (hFlush, stdout)
|
||||||
|
import System.Process (callCommand, 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 <name> <path>' 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 <path>' 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 quiet
|
||||||
|
Nothing -> autoDetectAndOpen expanded quiet
|
||||||
|
|
||||||
|
-- | 自动检测并打开文件管理器
|
||||||
|
autoDetectAndOpen :: FilePath -> Bool -> IO ()
|
||||||
|
autoDetectAndOpen path quiet = do
|
||||||
|
let (cmd, args) = case os of
|
||||||
|
"darwin" -> ("open", [path])
|
||||||
|
"mingw32" -> ("explorer", [path])
|
||||||
|
"mingw64" -> ("explorer", [path])
|
||||||
|
"cygwin" -> ("cygstart", [path])
|
||||||
|
_ -> ("xdg-open", [path]) -- 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 <command>"
|
||||||
|
-- 输出 cd 命令作为备选
|
||||||
|
putStrLn $ "cd " ++ show path
|
||||||
|
|
||||||
|
-- | 运行文件管理器
|
||||||
|
runFileManager :: FilePath -> FilePath -> Bool -> IO ()
|
||||||
|
runFileManager cmd path quiet = do
|
||||||
|
expanded <- expandPath path
|
||||||
|
let fullCmd = cmd ++ " " ++ show expanded
|
||||||
|
_ <- spawnCommand fullCmd >>= waitForProcess
|
||||||
|
return ()
|
||||||
|
|
||||||
|
-- | 运行配置命令
|
||||||
|
runConfigCmd :: ConfigAction -> Bool -> IO ()
|
||||||
|
runConfigCmd action quiet = do
|
||||||
|
cfg <- ensureConfigExists
|
||||||
|
case action of
|
||||||
|
ConfigAdd name path mDesc -> do
|
||||||
|
expanded <- expandPath path
|
||||||
|
exists <- doesDirectoryExist expanded
|
||||||
|
unless exists $ do
|
||||||
|
unless quiet $ putStrLn $ "Warning: Directory does not exist: " ++ expanded
|
||||||
|
let entry = JumpEntry
|
||||||
|
{ path = path
|
||||||
|
, description = mDesc
|
||||||
|
, priority = 100
|
||||||
|
}
|
||||||
|
newCfg = cfg { entries = M.insert name entry (entries cfg) }
|
||||||
|
saveConfig newCfg
|
||||||
|
unless quiet $ putStrLn $ "Added '" ++ T.unpack name ++ "' -> " ++ path
|
||||||
|
|
||||||
|
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 path -> do
|
||||||
|
expanded <- expandPath path
|
||||||
|
exists <- doesDirectoryExist expanded
|
||||||
|
unless exists $ do
|
||||||
|
unless quiet $ putStrLn $ "Warning: Directory does not exist: " ++ expanded
|
||||||
|
let newCfg = cfg { defaultPath = Just path }
|
||||||
|
saveConfig newCfg
|
||||||
|
unless quiet $ putStrLn $ "Set default path to: " ++ path
|
||||||
|
|
||||||
|
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 path -> do
|
||||||
|
saveConfigTo path cfg
|
||||||
|
unless quiet $ putStrLn $ "Config exported to: " ++ path
|
||||||
|
|
||||||
|
ConfigImport path merge -> do
|
||||||
|
imported <- loadConfigFrom path
|
||||||
|
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', $_)"
|
||||||
|
, " }"
|
||||||
|
, "}"
|
||||||
|
]
|
||||||
168
app/Config.hs
Normal file
168
app/Config.hs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Config
|
||||||
|
( getConfigPath
|
||||||
|
, loadConfig
|
||||||
|
, loadConfigFrom
|
||||||
|
, saveConfig
|
||||||
|
, saveConfigTo
|
||||||
|
, ensureConfigExists
|
||||||
|
, findEntry
|
||||||
|
, expandPath
|
||||||
|
, getSortedEntries
|
||||||
|
, mergeConfigs
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Exception (catch, throwIO)
|
||||||
|
import Control.Monad (unless, when)
|
||||||
|
import Data.Aeson (eitherDecode, encode)
|
||||||
|
import Data.Aeson.Encode.Pretty (encodePretty)
|
||||||
|
import Data.Bifunctor (first)
|
||||||
|
import Data.List (sortOn)
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import System.Directory (createDirectoryIfMissing, doesFileExist,
|
||||||
|
getHomeDirectory)
|
||||||
|
import System.Environment (lookupEnv)
|
||||||
|
import System.FilePath ((</>), takeDirectory)
|
||||||
|
import System.IO.Error (isDoesNotExistError)
|
||||||
|
|
||||||
|
import Types
|
||||||
|
|
||||||
|
-- | 获取配置文件路径
|
||||||
|
getConfigPath :: IO FilePath
|
||||||
|
getConfigPath = do
|
||||||
|
-- 首先检查环境变量
|
||||||
|
mEnvPath <- lookupEnv "QUICKJUMP_CONFIG"
|
||||||
|
case mEnvPath of
|
||||||
|
Just path -> return path
|
||||||
|
Nothing -> do
|
||||||
|
-- 默认使用 XDG 配置目录
|
||||||
|
xdgConfig <- lookupEnv "XDG_CONFIG_HOME"
|
||||||
|
configDir <- case xdgConfig of
|
||||||
|
Just dir -> return dir
|
||||||
|
Nothing -> do
|
||||||
|
home <- getHomeDirectory
|
||||||
|
return $ home </> ".config"
|
||||||
|
return $ configDir </> "quickjump" </> "config.json"
|
||||||
|
|
||||||
|
-- | 展开路径中的 ~ 和环境变量
|
||||||
|
expandPath :: FilePath -> IO FilePath
|
||||||
|
expandPath path = do
|
||||||
|
-- 首先处理 ~ 展开
|
||||||
|
expanded1 <- if take 1 path == "~"
|
||||||
|
then do
|
||||||
|
home <- getHomeDirectory
|
||||||
|
return $ home </> drop 2 path
|
||||||
|
else return path
|
||||||
|
-- 然后处理环境变量(支持 Unix $VAR 和 Windows %VAR% 格式)
|
||||||
|
expandEnvVars expanded1
|
||||||
|
|
||||||
|
-- | 展开环境变量
|
||||||
|
expandEnvVars :: FilePath -> IO FilePath
|
||||||
|
expandEnvVars path = do
|
||||||
|
-- 处理 Unix 风格的环境变量 $VAR
|
||||||
|
let expandUnixVars s = case s of
|
||||||
|
'$':'{':rest ->
|
||||||
|
case break (=='}') rest of
|
||||||
|
(var, '}':remaining) -> do
|
||||||
|
mval <- lookupEnv var
|
||||||
|
case mval of
|
||||||
|
Just val -> (val ++) <$> expandEnvVars remaining
|
||||||
|
Nothing -> (("${" ++ var ++ "}") ++) <$> expandEnvVars remaining
|
||||||
|
_ -> ('$':) <$> expandEnvVars rest
|
||||||
|
'$':rest ->
|
||||||
|
case span (\c -> isAlphaNum c || c == '_') rest of
|
||||||
|
(var, remaining) -> do
|
||||||
|
mval <- lookupEnv var
|
||||||
|
case mval of
|
||||||
|
Just val -> (val ++) <$> expandEnvVars remaining
|
||||||
|
Nothing -> (('$' : var) ++) <$> expandEnvVars remaining
|
||||||
|
c:cs -> (c:) <$> expandEnvVars cs
|
||||||
|
[] -> return []
|
||||||
|
-- 处理 Windows 风格的环境变量 %VAR%
|
||||||
|
let expandWindowsVars s = case s of
|
||||||
|
'%':rest ->
|
||||||
|
case break (=='%') rest of
|
||||||
|
(var, '%':remaining) -> do
|
||||||
|
mval <- lookupEnv var
|
||||||
|
case mval of
|
||||||
|
Just val -> (val ++) <$> expandEnvVars remaining
|
||||||
|
Nothing -> (('%' : var ++ "%") ++) <$> expandEnvVars remaining
|
||||||
|
_ -> ('%':) <$> expandEnvVars rest
|
||||||
|
c:cs -> (c:) <$> expandEnvVars cs
|
||||||
|
[] -> return []
|
||||||
|
-- 根据操作系统选择展开方式
|
||||||
|
if '%' `elem` path
|
||||||
|
then expandWindowsVars path
|
||||||
|
else expandUnixVars path
|
||||||
|
where
|
||||||
|
isAlphaNum c = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
||||||
|
|
||||||
|
-- | 确保配置文件存在(如果不存在则创建默认配置)
|
||||||
|
ensureConfigExists :: IO Config
|
||||||
|
ensureConfigExists = do
|
||||||
|
configPath <- getConfigPath
|
||||||
|
exists <- doesFileExist configPath
|
||||||
|
if exists
|
||||||
|
then loadConfig
|
||||||
|
else do
|
||||||
|
putStrLn $ "Config not found, creating default at: " ++ configPath
|
||||||
|
createDirectoryIfMissing True (takeDirectory configPath)
|
||||||
|
saveConfig defaultConfig
|
||||||
|
return defaultConfig
|
||||||
|
|
||||||
|
-- | 加载配置
|
||||||
|
loadConfig :: IO Config
|
||||||
|
loadConfig = do
|
||||||
|
configPath <- getConfigPath
|
||||||
|
loadConfigFrom configPath
|
||||||
|
|
||||||
|
-- | 从指定路径加载配置
|
||||||
|
loadConfigFrom :: FilePath -> IO Config
|
||||||
|
loadConfigFrom path = do
|
||||||
|
expanded <- expandPath path
|
||||||
|
result <- catch
|
||||||
|
(Right <$> BL.readFile expanded)
|
||||||
|
(\e -> if isDoesNotExistError e
|
||||||
|
then return $ Left $ "Config file not found: " ++ expanded
|
||||||
|
else throwIO e)
|
||||||
|
case result of
|
||||||
|
Left err -> error err
|
||||||
|
Right bs ->
|
||||||
|
case eitherDecode bs of
|
||||||
|
Left err -> error $ "Failed to parse config: " ++ err
|
||||||
|
Right cfg -> return cfg
|
||||||
|
|
||||||
|
-- | 保存配置
|
||||||
|
saveConfig :: Config -> IO ()
|
||||||
|
saveConfig cfg = do
|
||||||
|
configPath <- getConfigPath
|
||||||
|
saveConfigTo configPath cfg
|
||||||
|
|
||||||
|
-- | 保存配置到指定路径
|
||||||
|
saveConfigTo :: FilePath -> Config -> IO ()
|
||||||
|
saveConfigTo path cfg = do
|
||||||
|
expanded <- expandPath path
|
||||||
|
createDirectoryIfMissing True (takeDirectory expanded)
|
||||||
|
BL.writeFile expanded (encodePretty cfg)
|
||||||
|
|
||||||
|
-- | 查找条目
|
||||||
|
findEntry :: Text -> Config -> Maybe JumpEntry
|
||||||
|
findEntry name cfg = M.lookup name (entries cfg)
|
||||||
|
|
||||||
|
-- | 获取按优先级排序的条目列表
|
||||||
|
getSortedEntries :: Config -> [(Text, JumpEntry)]
|
||||||
|
getSortedEntries cfg =
|
||||||
|
sortOn (priority . snd) $ M.toList (entries cfg)
|
||||||
|
|
||||||
|
-- | 合并两个配置(用于导入)
|
||||||
|
mergeConfigs :: Config -> Config -> Bool -> Config
|
||||||
|
mergeConfigs base new shouldMerge =
|
||||||
|
if shouldMerge
|
||||||
|
then base { entries = M.union (entries new) (entries base) }
|
||||||
|
else new
|
||||||
190
app/Main.hs
Normal file
190
app/Main.hs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
-- import Data.Text (Text)
|
||||||
|
-- import qualified Data.Text as T
|
||||||
|
import Options.Applicative
|
||||||
|
import System.Environment (getArgs, withArgs)
|
||||||
|
-- import System.IO (hPutStrLn, stderr)
|
||||||
|
|
||||||
|
import Commands
|
||||||
|
import Types
|
||||||
|
|
||||||
|
-- | 主函数
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
-- 如果没有参数,显示帮助
|
||||||
|
if null args
|
||||||
|
then withArgs ["--help"] runParser
|
||||||
|
else runParser
|
||||||
|
where
|
||||||
|
runParser = do
|
||||||
|
cmd <- execParser opts
|
||||||
|
runCommand cmd
|
||||||
|
|
||||||
|
opts = info (helper <*> versionOption <*> commandParser)
|
||||||
|
( fullDesc
|
||||||
|
<> progDesc "QuickJump - Fast directory navigation tool"
|
||||||
|
<> header "quickjump - A command line tool for quick directory jumping" )
|
||||||
|
|
||||||
|
-- | 静默模式选项
|
||||||
|
quietOption :: Parser Bool
|
||||||
|
quietOption = switch
|
||||||
|
( long "quiet"
|
||||||
|
<> short 'q'
|
||||||
|
<> help "Suppress output messages (quiet mode)" )
|
||||||
|
|
||||||
|
-- | 版本选项
|
||||||
|
versionOption :: Parser (a -> a)
|
||||||
|
versionOption = infoOption "quickjump 0.3.0.1"
|
||||||
|
( long "version"
|
||||||
|
<> short 'v'
|
||||||
|
<> help "Show version information" )
|
||||||
|
|
||||||
|
-- | 主命令解析器
|
||||||
|
commandParser :: Parser Command
|
||||||
|
commandParser = subparser
|
||||||
|
( command "jump" (info jumpParser
|
||||||
|
( progDesc "Jump to a configured directory" ))
|
||||||
|
<> command "j" (info jumpParser
|
||||||
|
( progDesc "Alias for jump" ))
|
||||||
|
<> command "quick" (info quickParser
|
||||||
|
( progDesc "Quick open a directory" ))
|
||||||
|
<> command "k" (info quickParser
|
||||||
|
( progDesc "Alias for quick" ))
|
||||||
|
<> command "config" (info configParser
|
||||||
|
( progDesc "Manage configuration" ))
|
||||||
|
<> command "c" (info configParser
|
||||||
|
( progDesc "Alias for config" ))
|
||||||
|
<> command "shell-integration" (info shellIntegrationParser
|
||||||
|
( progDesc "Output shell integration script" ))
|
||||||
|
)
|
||||||
|
<|> jumpParser -- 默认命令是 jump
|
||||||
|
|
||||||
|
-- | 跳转命令解析器
|
||||||
|
jumpParser :: Parser Command
|
||||||
|
jumpParser = (Jump <$> argument str
|
||||||
|
( metavar "NAME"
|
||||||
|
<> help "Name of the jump target" ))
|
||||||
|
<*> quietOption
|
||||||
|
<|> (JumpInteractive <$> flag' False
|
||||||
|
( long "interactive"
|
||||||
|
<> short 'i'
|
||||||
|
<> help "Interactive mode - select from list" ))
|
||||||
|
|
||||||
|
-- | 快速命令解析器
|
||||||
|
quickParser :: Parser Command
|
||||||
|
quickParser = (Quick <$> subparser
|
||||||
|
( command "open" (info (QuickOpen <$> argument str (metavar "NAME"))
|
||||||
|
( progDesc "Open a configured directory" ))
|
||||||
|
<> command "list" (info (pure QuickList)
|
||||||
|
( progDesc "List all quick access targets" ))
|
||||||
|
<> command "default" (info (pure QuickDefault)
|
||||||
|
( progDesc "Open the default directory" ))
|
||||||
|
)
|
||||||
|
<*> quietOption)
|
||||||
|
<|> (Quick <$> (QuickOpen <$> strOption
|
||||||
|
( long "open"
|
||||||
|
<> short 'o'
|
||||||
|
<> metavar "NAME"
|
||||||
|
<> help "Open the specified target" ))
|
||||||
|
<*> quietOption)
|
||||||
|
<|> (Quick <$> (QuickOpenPath <$> strOption
|
||||||
|
( long "path"
|
||||||
|
<> short 'p'
|
||||||
|
<> metavar "PATH"
|
||||||
|
<> help "Open the specified path" ))
|
||||||
|
<*> quietOption)
|
||||||
|
<|> (Quick <$> flag' QuickList
|
||||||
|
( long "list"
|
||||||
|
<> short 'l'
|
||||||
|
<> help "List all targets" )
|
||||||
|
<*> quietOption)
|
||||||
|
<|> (Quick <$> flag' QuickDefault
|
||||||
|
( long "default"
|
||||||
|
<> short 'd'
|
||||||
|
<> help "Open default directory" )
|
||||||
|
<*> quietOption)
|
||||||
|
<|> (Quick <$> (QuickOpen <$> argument str (metavar "NAME" <> help "Target name or path"))
|
||||||
|
<*> quietOption)
|
||||||
|
|
||||||
|
-- | 配置命令解析器
|
||||||
|
configParser :: Parser Command
|
||||||
|
configParser = ConfigCmd <$> subparser
|
||||||
|
( command "add" (info addParser
|
||||||
|
( progDesc "Add a new jump entry" ))
|
||||||
|
<> command "remove" (info removeParser
|
||||||
|
( progDesc "Remove a jump entry" ))
|
||||||
|
<> command "rm" (info removeParser
|
||||||
|
( progDesc "Alias for remove" ))
|
||||||
|
<> command "list" (info (pure ConfigList)
|
||||||
|
( progDesc "List all entries" ))
|
||||||
|
<> command "ls" (info (pure ConfigList)
|
||||||
|
( progDesc "Alias for list" ))
|
||||||
|
<> command "set-default" (info setDefaultParser
|
||||||
|
( progDesc "Set the default path" ))
|
||||||
|
<> command "set-editor" (info setEditorParser
|
||||||
|
( progDesc "Set the preferred editor" ))
|
||||||
|
<> command "set-file-manager" (info setFileManagerParser
|
||||||
|
( progDesc "Set the preferred file manager" ))
|
||||||
|
<> command "export" (info exportParser
|
||||||
|
( progDesc "Export configuration to file" ))
|
||||||
|
<> command "import" (info importParser
|
||||||
|
( progDesc "Import configuration from file" ))
|
||||||
|
<> command "edit" (info (pure ConfigEdit)
|
||||||
|
( progDesc "Edit configuration with editor" ))
|
||||||
|
<> command "show" (info (pure ConfigShow)
|
||||||
|
( progDesc "Show current configuration" ))
|
||||||
|
)
|
||||||
|
<*> quietOption
|
||||||
|
|
||||||
|
-- | 添加条目解析器
|
||||||
|
addParser :: Parser ConfigAction
|
||||||
|
addParser = ConfigAdd
|
||||||
|
<$> argument str (metavar "NAME" <> help "Entry name")
|
||||||
|
<*> argument str (metavar "PATH" <> help "Directory path")
|
||||||
|
<*> optional (strOption
|
||||||
|
( long "description"
|
||||||
|
<> short 'd'
|
||||||
|
<> metavar "DESC"
|
||||||
|
<> help "Optional description" ))
|
||||||
|
|
||||||
|
-- | 删除条目解析器
|
||||||
|
removeParser :: Parser ConfigAction
|
||||||
|
removeParser = ConfigRemove
|
||||||
|
<$> argument str (metavar "NAME" <> help "Entry name to remove")
|
||||||
|
|
||||||
|
-- | 设置默认路径解析器
|
||||||
|
setDefaultParser :: Parser ConfigAction
|
||||||
|
setDefaultParser = ConfigSetDefault
|
||||||
|
<$> argument str (metavar "PATH" <> help "Default directory path")
|
||||||
|
|
||||||
|
-- | 设置编辑器解析器
|
||||||
|
setEditorParser :: Parser ConfigAction
|
||||||
|
setEditorParser = ConfigSetEditor
|
||||||
|
<$> argument str (metavar "COMMAND" <> help "Editor command")
|
||||||
|
|
||||||
|
-- | 设置文件管理器解析器
|
||||||
|
setFileManagerParser :: Parser ConfigAction
|
||||||
|
setFileManagerParser = ConfigSetFileManager
|
||||||
|
<$> argument str (metavar "COMMAND" <> help "File manager command")
|
||||||
|
|
||||||
|
-- | 导出配置解析器
|
||||||
|
exportParser :: Parser ConfigAction
|
||||||
|
exportParser = ConfigExport
|
||||||
|
<$> argument str (metavar "FILE" <> help "Export file path")
|
||||||
|
|
||||||
|
-- | 导入配置解析器
|
||||||
|
importParser :: Parser ConfigAction
|
||||||
|
importParser = ConfigImport
|
||||||
|
<$> argument str (metavar "FILE" <> help "Import file path")
|
||||||
|
<*> switch
|
||||||
|
( long "merge"
|
||||||
|
<> short 'm'
|
||||||
|
<> help "Merge with existing config instead of replacing" )
|
||||||
|
|
||||||
|
-- | Shell 集成解析器
|
||||||
|
shellIntegrationParser :: Parser Command
|
||||||
|
shellIntegrationParser = pure ShellIntegration
|
||||||
132
app/Types.hs
Normal file
132
app/Types.hs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Types
|
||||||
|
( Config(..)
|
||||||
|
, JumpEntry(..)
|
||||||
|
, Command(..)
|
||||||
|
, QuickAction(..)
|
||||||
|
, ConfigAction(..)
|
||||||
|
, defaultConfig
|
||||||
|
, emptyConfig
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Data.Text (Text)
|
||||||
|
import GHC.Generics
|
||||||
|
|
||||||
|
-- | 单个跳转条目
|
||||||
|
data JumpEntry = JumpEntry
|
||||||
|
{ path :: FilePath -- ^ 目标路径
|
||||||
|
, description :: Maybe Text -- ^ 可选描述
|
||||||
|
, priority :: Int -- ^ 优先级(数字越小越优先)
|
||||||
|
} deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
instance ToJSON JumpEntry where
|
||||||
|
toJSON entry = object
|
||||||
|
[ "path" .= path entry
|
||||||
|
, "description" .= description entry
|
||||||
|
, "priority" .= priority entry
|
||||||
|
]
|
||||||
|
|
||||||
|
instance FromJSON JumpEntry where
|
||||||
|
parseJSON = withObject "JumpEntry" $ \v -> JumpEntry
|
||||||
|
<$> v .: "path"
|
||||||
|
<*> v .:? "description"
|
||||||
|
<*> v .:? "priority" .!= 100
|
||||||
|
|
||||||
|
-- | 配置文件结构
|
||||||
|
data Config = Config
|
||||||
|
{ version :: Text -- ^ 配置版本
|
||||||
|
, entries :: Map Text JumpEntry -- ^ 命名跳转条目
|
||||||
|
, defaultPath :: Maybe FilePath -- ^ 默认打开路径
|
||||||
|
, editor :: Maybe FilePath -- ^ 首选编辑器
|
||||||
|
, fileManager :: Maybe FilePath -- ^ 首选文件管理器
|
||||||
|
} deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
instance ToJSON Config where
|
||||||
|
toJSON cfg = object
|
||||||
|
[ "version" .= version cfg
|
||||||
|
, "entries" .= entries cfg
|
||||||
|
, "default_path" .= defaultPath cfg
|
||||||
|
, "editor" .= editor cfg
|
||||||
|
, "file_manager" .= fileManager cfg
|
||||||
|
]
|
||||||
|
|
||||||
|
instance FromJSON Config where
|
||||||
|
parseJSON = withObject "Config" $ \v -> Config
|
||||||
|
<$> v .:? "version" .!= "1.0"
|
||||||
|
<*> v .:? "entries" .!= M.empty
|
||||||
|
<*> v .:? "default_path"
|
||||||
|
<*> v .:? "editor"
|
||||||
|
<*> v .:? "file_manager"
|
||||||
|
|
||||||
|
-- | 快速操作类型
|
||||||
|
data QuickAction
|
||||||
|
= QuickOpen Text -- ^ 打开配置中的指定条目
|
||||||
|
| QuickOpenPath FilePath -- ^ 打开指定路径
|
||||||
|
| QuickList -- ^ 列出所有快速条目
|
||||||
|
| QuickDefault -- ^ 打开默认路径
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- | 配置操作类型
|
||||||
|
data ConfigAction
|
||||||
|
= ConfigAdd Text FilePath (Maybe Text) -- ^ 添加条目: 名称 路径 [描述]
|
||||||
|
| ConfigRemove Text -- ^ 删除条目
|
||||||
|
| ConfigList -- ^ 列出所有条目
|
||||||
|
| ConfigSetDefault FilePath -- ^ 设置默认路径
|
||||||
|
| ConfigSetEditor FilePath -- ^ 设置编辑器
|
||||||
|
| ConfigSetFileManager FilePath -- ^ 设置文件管理器
|
||||||
|
| ConfigExport FilePath -- ^ 导出配置到文件
|
||||||
|
| ConfigImport FilePath Bool -- ^ 导入配置 (文件路径, 是否合并)
|
||||||
|
| ConfigEdit -- ^ 用编辑器打开配置文件
|
||||||
|
| ConfigShow -- ^ 显示当前配置
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- | 主命令类型
|
||||||
|
data Command
|
||||||
|
= Jump Text Bool -- ^ 跳转到指定条目 (名称, 是否静默)
|
||||||
|
| JumpInteractive Bool -- ^ 交互式选择跳转 (是否静默)
|
||||||
|
| Quick QuickAction Bool -- ^ 快速操作 (操作, 是否静默)
|
||||||
|
| ConfigCmd ConfigAction Bool -- ^ 配置操作 (操作, 是否静默)
|
||||||
|
| ShellIntegration -- ^ 输出 shell 集成脚本
|
||||||
|
| Version -- ^ 显示版本
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- | 空配置
|
||||||
|
emptyConfig :: Config
|
||||||
|
emptyConfig = Config
|
||||||
|
{ version = "1.0"
|
||||||
|
, entries = M.empty
|
||||||
|
, defaultPath = Nothing
|
||||||
|
, editor = Nothing
|
||||||
|
, fileManager = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | 默认配置(带示例)
|
||||||
|
defaultConfig :: Config
|
||||||
|
defaultConfig = Config
|
||||||
|
{ version = "1.0"
|
||||||
|
, entries = M.fromList
|
||||||
|
[ ("home", JumpEntry
|
||||||
|
{ path = "~"
|
||||||
|
, description = Just "Home directory"
|
||||||
|
, priority = 1
|
||||||
|
})
|
||||||
|
, ("docs", JumpEntry
|
||||||
|
{ path = "~/Documents"
|
||||||
|
, description = Just "Documents folder"
|
||||||
|
, priority = 2
|
||||||
|
})
|
||||||
|
, ("downloads", JumpEntry
|
||||||
|
{ path = "~/Downloads"
|
||||||
|
, description = Just "Downloads folder"
|
||||||
|
, priority = 3
|
||||||
|
})
|
||||||
|
]
|
||||||
|
, defaultPath = Just "~"
|
||||||
|
, editor = Just "vim"
|
||||||
|
, fileManager = Nothing
|
||||||
|
}
|
||||||
88
app/Utils.hs
Normal file
88
app/Utils.hs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Utils
|
||||||
|
( padRight
|
||||||
|
, commandExists
|
||||||
|
, formatTable
|
||||||
|
, truncatePath
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Exception (catch)
|
||||||
|
import Data.List (intercalate, transpose)
|
||||||
|
import System.Directory (findExecutable)
|
||||||
|
import System.IO.Error (isDoesNotExistError)
|
||||||
|
|
||||||
|
-- | 右填充字符串到指定长度
|
||||||
|
padRight :: Int -> String -> String
|
||||||
|
padRight n s = s ++ replicate (max 0 (n - length s)) ' '
|
||||||
|
|
||||||
|
-- | 左填充字符串到指定长度
|
||||||
|
padLeft :: Int -> String -> String
|
||||||
|
padLeft n s = replicate (max 0 (n - length s)) ' ' ++ s
|
||||||
|
|
||||||
|
-- | 检查命令是否存在
|
||||||
|
commandExists :: String -> IO Bool
|
||||||
|
commandExists cmd = do
|
||||||
|
result <- findExecutable cmd
|
||||||
|
return $ case result of
|
||||||
|
Just _ -> True
|
||||||
|
Nothing -> False
|
||||||
|
|
||||||
|
-- | 截断路径显示
|
||||||
|
truncatePath :: Int -> String -> String
|
||||||
|
truncatePath maxLen path
|
||||||
|
| length path <= maxLen = path
|
||||||
|
| otherwise = "..." ++ drop (length path - maxLen + 3) path
|
||||||
|
|
||||||
|
-- | 格式化表格
|
||||||
|
data TableCell = TableCell String Int -- ^ 内容和对齐宽度
|
||||||
|
|
||||||
|
formatTable :: [[String]] -> String
|
||||||
|
formatTable rows =
|
||||||
|
let -- 计算每列的最大宽度
|
||||||
|
colWidths = map maximum $ transpose
|
||||||
|
[ map length row | row <- rows ]
|
||||||
|
-- 格式化每一行
|
||||||
|
formatRow row = intercalate " "
|
||||||
|
[ padRight w cell | (cell, w) <- zip row colWidths ]
|
||||||
|
in intercalate "\n" $ map formatRow rows
|
||||||
|
|
||||||
|
-- | 安全的读取文件
|
||||||
|
safeReadFile :: FilePath -> IO (Maybe String)
|
||||||
|
safeReadFile path = do
|
||||||
|
result <- catch
|
||||||
|
(Just <$> readFile path)
|
||||||
|
(\e -> if isDoesNotExistError e then return Nothing else return Nothing)
|
||||||
|
return result
|
||||||
|
|
||||||
|
-- | 字符串居中
|
||||||
|
center :: Int -> String -> String
|
||||||
|
center width s =
|
||||||
|
let padding = max 0 (width - length s)
|
||||||
|
leftPad = padding `div` 2
|
||||||
|
rightPad = padding - leftPad
|
||||||
|
in replicate leftPad ' ' ++ s ++ replicate rightPad ' '
|
||||||
|
|
||||||
|
-- | 重复字符串
|
||||||
|
repeatString :: Int -> String -> String
|
||||||
|
repeatString n = concat . replicate n
|
||||||
|
|
||||||
|
-- | 高亮文本(终端颜色)
|
||||||
|
highlight :: String -> String
|
||||||
|
highlight s = "\ESC[1m" ++ s ++ "\ESC[0m"
|
||||||
|
|
||||||
|
-- | 绿色文本
|
||||||
|
green :: String -> String
|
||||||
|
green s = "\ESC[32m" ++ s ++ "\ESC[0m"
|
||||||
|
|
||||||
|
-- | 黄色文本
|
||||||
|
yellow :: String -> String
|
||||||
|
yellow s = "\ESC[33m" ++ s ++ "\ESC[0m"
|
||||||
|
|
||||||
|
-- | 红色文本
|
||||||
|
red :: String -> String
|
||||||
|
red s = "\ESC[31m" ++ s ++ "\ESC[0m"
|
||||||
|
|
||||||
|
-- | 蓝色文本
|
||||||
|
blue :: String -> String
|
||||||
|
blue s = "\ESC[34m" ++ s ++ "\ESC[0m"
|
||||||
92
quickjump.cabal
Normal file
92
quickjump.cabal
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
cabal-version: 3.4
|
||||||
|
-- The cabal-version field refers to the version of the .cabal specification,
|
||||||
|
-- and can be different from the cabal-install (the tool) version and the
|
||||||
|
-- Cabal (the library) version you are using. As such, the Cabal (the library)
|
||||||
|
-- version used must be equal or greater than the version stated in this field.
|
||||||
|
-- Starting from the specification version 2.2, the cabal-version field must be
|
||||||
|
-- the first thing in the cabal file.
|
||||||
|
|
||||||
|
-- Initial package description 'quickjump' generated by
|
||||||
|
-- 'cabal init'. For further documentation, see:
|
||||||
|
-- http://haskell.org/cabal/users-guide/
|
||||||
|
--
|
||||||
|
-- The name of the package.
|
||||||
|
name: quickjump
|
||||||
|
|
||||||
|
-- The package version.
|
||||||
|
-- See the Haskell package versioning policy (PVP) for standards
|
||||||
|
-- guiding when and how versions should be incremented.
|
||||||
|
-- https://pvp.haskell.org
|
||||||
|
-- PVP summary: +-+------- breaking API changes
|
||||||
|
-- | | +----- non-breaking API additions
|
||||||
|
-- | | | +--- code changes with no API change
|
||||||
|
version: 0.3.0.1
|
||||||
|
|
||||||
|
-- A short (one-line) description of the package.
|
||||||
|
synopsis: Directory Jump and Quick Directory Open
|
||||||
|
|
||||||
|
-- A longer description of the package.
|
||||||
|
description: A command line tool for fast directory navigation and configuration management
|
||||||
|
|
||||||
|
-- URL for the project homepage or repository.
|
||||||
|
homepage: git.lyz.one/sidneyzhang
|
||||||
|
|
||||||
|
-- The license under which the package is released.
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
-- The file containing the license text.
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
-- The package author(s).
|
||||||
|
author: Sidney Zhang
|
||||||
|
|
||||||
|
-- An email address to which users can send suggestions, bug reports, and patches.
|
||||||
|
maintainer: zly@lyzhang.me
|
||||||
|
|
||||||
|
-- A copyright notice.
|
||||||
|
-- copyright:
|
||||||
|
category: System
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
|
||||||
|
extra-doc-files: CHANGELOG.md
|
||||||
|
|
||||||
|
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
|
||||||
|
-- extra-source-files:
|
||||||
|
|
||||||
|
common warnings
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
executable quickjump
|
||||||
|
-- Import common warning flags.
|
||||||
|
import: warnings
|
||||||
|
|
||||||
|
-- .hs or .lhs file containing the Main module.
|
||||||
|
main-is: Main.hs
|
||||||
|
|
||||||
|
-- Modules included in this executable, other than Main.
|
||||||
|
other-modules: Commands
|
||||||
|
, Config
|
||||||
|
, Types
|
||||||
|
, Utils
|
||||||
|
|
||||||
|
-- LANGUAGE extensions used by modules in this package.
|
||||||
|
-- other-extensions:
|
||||||
|
|
||||||
|
-- Other library packages from which modules are imported.
|
||||||
|
build-depends: aeson ^>=2.2.3.0
|
||||||
|
, aeson-pretty ^>=0.8.10
|
||||||
|
, base ^>=4.18.3.0
|
||||||
|
, bytestring ^>=0.11.5.0
|
||||||
|
, containers ^>=0.6.7
|
||||||
|
, directory ^>=1.3.8.0
|
||||||
|
, filepath ^>=1.4.300.0
|
||||||
|
, optparse-applicative ^>=0.18.1.0
|
||||||
|
, process ^>=1.6.18.0
|
||||||
|
, text ^>=2.0.2
|
||||||
|
|
||||||
|
-- Directories containing source files.
|
||||||
|
hs-source-dirs: app
|
||||||
|
|
||||||
|
-- Base language which the package is written in.
|
||||||
|
default-language: Haskell2010
|
||||||
Reference in New Issue
Block a user