280 lines
5.8 KiB
Go
280 lines
5.8 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/yeho/klp/internal/clipboard"
|
|
"github.com/yeho/klp/internal/config"
|
|
"github.com/yeho/klp/internal/database"
|
|
"github.com/yeho/klp/internal/search"
|
|
"github.com/yeho/klp/internal/tui"
|
|
)
|
|
|
|
var (
|
|
Version = "dev" // Set at build time via ldflags
|
|
|
|
versionFlag bool
|
|
listFlag bool
|
|
listLimitFlag int
|
|
limitFlag int
|
|
searchFlag string
|
|
deleteFlag string
|
|
|
|
cfg *config.Config
|
|
)
|
|
|
|
var rootCmd = &cobra.Command{
|
|
Use: "klp [id]",
|
|
Short: "A clipboard history manager for Linux",
|
|
Long: `klp is a clipboard history manager that monitors clipboard changes
|
|
and provides quick access to your clipboard history.`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
if versionFlag {
|
|
fmt.Println(Version)
|
|
return
|
|
}
|
|
|
|
if deleteFlag != "" {
|
|
deleteEntry(deleteFlag)
|
|
return
|
|
}
|
|
|
|
if searchFlag != "" {
|
|
searchEntries(searchFlag)
|
|
return
|
|
}
|
|
|
|
// If ID provided as argument, copy that entry to clipboard
|
|
if len(args) == 1 {
|
|
copyEntryToClipboard(args[0])
|
|
return
|
|
}
|
|
|
|
if listFlag {
|
|
// Non-interactive list
|
|
limit := cfg.DefaultLimit
|
|
if listLimitFlag > 0 {
|
|
limit = listLimitFlag
|
|
}
|
|
listEntriesNonInteractive(limit)
|
|
return
|
|
}
|
|
|
|
// Default: interactive list
|
|
limit := cfg.DefaultLimit
|
|
if limitFlag > 0 {
|
|
limit = limitFlag
|
|
}
|
|
listEntriesInteractive(limit)
|
|
},
|
|
}
|
|
|
|
func Execute() {
|
|
if err := rootCmd.Execute(); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
cobra.OnInitialize(initConfig)
|
|
|
|
rootCmd.Flags().BoolVarP(&versionFlag, "version", "v", false, "Print version")
|
|
rootCmd.Flags().BoolVarP(&listFlag, "list", "l", false, "Non-interactive list output")
|
|
rootCmd.Flags().IntVarP(&listLimitFlag, "list-limit", "n", 0, "Limit for non-interactive list (use with -l)")
|
|
rootCmd.Flags().IntVar(&limitFlag, "limit", 0, "Limit for interactive list")
|
|
rootCmd.Flags().StringVarP(&searchFlag, "search", "s", "", "Search clipboard entries")
|
|
rootCmd.Flags().StringVarP(&deleteFlag, "delete", "d", "", "Delete entry by ID")
|
|
}
|
|
|
|
func initConfig() {
|
|
var err error
|
|
cfg, err = config.Load()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func loadDatabase() *database.Database {
|
|
db, err := database.Load(cfg.DBPath())
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error loading database: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return db
|
|
}
|
|
|
|
func copyEntryToClipboard(id string) {
|
|
db := loadDatabase()
|
|
|
|
entry, err := db.Get(id)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Entry not found: %s\n", id)
|
|
os.Exit(1)
|
|
}
|
|
|
|
clip, err := clipboard.New()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error initializing clipboard: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := clip.Write(entry.Value); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error writing to clipboard: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
preview := entry.Value
|
|
if len(preview) > 50 {
|
|
preview = preview[:50] + "..."
|
|
}
|
|
fmt.Printf("Copied to clipboard: %s\n", cleanForDisplay(preview))
|
|
}
|
|
|
|
func deleteEntry(id string) {
|
|
db := loadDatabase()
|
|
|
|
if err := db.Delete(id); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error deleting entry: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Deleted entry: %s\n", id)
|
|
}
|
|
|
|
func searchEntries(query string) {
|
|
db := loadDatabase()
|
|
searcher := search.NewSearcher(db)
|
|
|
|
limit := cfg.DefaultLimit
|
|
if limitFlag > 0 {
|
|
limit = limitFlag
|
|
}
|
|
|
|
results := searcher.Search(query, search.Options{MaxResults: limit})
|
|
|
|
if len(results) == 0 {
|
|
fmt.Printf("No results found for '%s'\n", query)
|
|
return
|
|
}
|
|
|
|
// Convert results to entries for display
|
|
entries := make([]*database.Entry, len(results))
|
|
for i, r := range results {
|
|
entries[i] = r.Entry
|
|
}
|
|
|
|
if listFlag {
|
|
// Non-interactive search results
|
|
printEntries(entries)
|
|
return
|
|
}
|
|
|
|
// Interactive search results
|
|
title := fmt.Sprintf("Search results for '%s' (%d found)", query, len(results))
|
|
selected, err := tui.Run(entries, db, title)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error running TUI: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if selected != nil {
|
|
copyToClipboard(selected)
|
|
}
|
|
}
|
|
|
|
func listEntriesNonInteractive(limit int) {
|
|
db := loadDatabase()
|
|
entries := db.List(limit)
|
|
|
|
if len(entries) == 0 {
|
|
fmt.Println("No clipboard entries found")
|
|
return
|
|
}
|
|
|
|
printEntries(entries)
|
|
}
|
|
|
|
func listEntriesInteractive(limit int) {
|
|
db := loadDatabase()
|
|
entries := db.List(limit)
|
|
|
|
if len(entries) == 0 {
|
|
fmt.Println("No clipboard entries found")
|
|
return
|
|
}
|
|
|
|
title := fmt.Sprintf("Clipboard History (%d entries)", len(entries))
|
|
selected, err := tui.Run(entries, db, title)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error running TUI: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if selected != nil {
|
|
copyToClipboard(selected)
|
|
}
|
|
}
|
|
|
|
func printEntries(entries []*database.Entry) {
|
|
for i, entry := range entries {
|
|
preview := entry.Value
|
|
if len(preview) > 60 {
|
|
preview = preview[:60] + "..."
|
|
}
|
|
preview = cleanForDisplay(preview)
|
|
|
|
fmt.Printf("%s. [%s] %s %s\n",
|
|
padLeft(strconv.Itoa(i+1), 2),
|
|
entry.ID,
|
|
entry.DateTime.Format("01/02 15:04"),
|
|
preview,
|
|
)
|
|
}
|
|
}
|
|
|
|
func copyToClipboard(entry *database.Entry) {
|
|
clip, err := clipboard.New()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error initializing clipboard: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := clip.Write(entry.Value); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error writing to clipboard: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
preview := entry.Value
|
|
if len(preview) > 50 {
|
|
preview = preview[:50] + "..."
|
|
}
|
|
fmt.Printf("Copied to clipboard: %s\n", cleanForDisplay(preview))
|
|
}
|
|
|
|
func cleanForDisplay(s string) string {
|
|
result := make([]byte, 0, len(s))
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == '\n' || s[i] == '\r' {
|
|
result = append(result, ' ')
|
|
} else if s[i] == '\t' {
|
|
result = append(result, ' ')
|
|
} else {
|
|
result = append(result, s[i])
|
|
}
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
func padLeft(s string, width int) string {
|
|
for len(s) < width {
|
|
s = " " + s
|
|
}
|
|
return s
|
|
}
|