1 Commits

Author SHA1 Message Date
viljarb
44f192b619 using h/l for navigation and arrow keys for moving issues in the tui kanban 2026-04-01 12:30:37 +02:00
5 changed files with 13 additions and 215 deletions

View File

@@ -72,7 +72,6 @@ func init() {
issues.UpdateCmd.GroupID = "issues"
issues.CloseCmd.GroupID = "issues"
issues.DeleteCmd.GroupID = "issues"
issues.DepCmd.GroupID = "issues"
RootCmd.AddCommand(issues.CreateCmd)
RootCmd.AddCommand(issues.GetCmd)
@@ -80,7 +79,6 @@ func init() {
RootCmd.AddCommand(issues.UpdateCmd)
RootCmd.AddCommand(issues.CloseCmd)
RootCmd.AddCommand(issues.DeleteCmd)
RootCmd.AddCommand(issues.DepCmd)
RootCmd.AddGroup(&cobra.Group{ID: "comment", Title: "Comment Management"})
issues.CommentCmd.GroupID = "comment"

View File

@@ -1,123 +0,0 @@
package issues
import (
"fmt"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/spf13/cobra"
)
const depCmdExample = `pm dep ISSUE-1
pm dep view ISSUE-1
pm dep add ISSUE-1 ISSUE-2
pm dep remove ISSUE-1 ISSUE-2`
// DepCmd manages dependencies for issues.
// Default behavior: view dependencies of an issue.
var DepCmd = &cobra.Command{
Use: "dep [issue id]",
Short: "Manage dependencies for an issue",
Long: `Manage dependencies between issues.`,
Aliases: []string{"dependencies"},
Example: depCmdExample,
// This command runs only when no subcommand is provided.
Args: cobra.ExactArgs(1),
ValidArgsFunction: completeIssues,
RunE: runDepViewCmd,
}
var DepViewCmd = &cobra.Command{
Use: "view [issue id]",
Short: "View dependencies of an issue",
Aliases: []string{"show"},
Args: cobra.ExactArgs(1),
ValidArgsFunction: completeIssues,
RunE: runDepViewCmd,
}
var DepAddCmd = &cobra.Command{
Use: "add [issue id] [depends-on id]",
Short: "Add a dependency: issue depends on depends-on issue",
Args: cobra.ExactArgs(2),
ValidArgsFunction: completeIssues,
RunE: runDepAddCmd,
}
var DepRemoveCmd = &cobra.Command{
Use: "remove [issue id] [depends-on id]",
Short: "Remove an existing dependency",
Aliases: []string{"rm"},
Args: cobra.ExactArgs(2),
ValidArgsFunction: completeIssues,
RunE: runDepRemoveCmd,
}
func init() {
DepCmd.AddCommand(DepViewCmd)
DepCmd.AddCommand(DepAddCmd)
DepCmd.AddCommand(DepRemoveCmd)
}
func runDepViewCmd(cmd *cobra.Command, args []string) error {
issueID := args[0]
app := AppFromContext(cmd.Context())
deps, err := app.Issues.GetDependencies(cmd.Context(), issueID)
if err != nil {
return fmt.Errorf("failed to load dependencies for %s: %w", issueID, err)
}
if len(deps) == 0 {
cmd.Printf("Issue %s has no dependencies.\n", issueID)
return nil
}
cmd.Printf("Dependencies for %s:\n", issueID)
for _, d := range deps {
if d == nil {
continue
}
if d.Title != "" {
cmd.Printf("- %s: %s\n", d.ID, d.Title)
} else {
cmd.Printf("- %s\n", d.ID)
}
}
return nil
}
func runDepAddCmd(cmd *cobra.Command, args []string) error {
issueID := args[0]
dependsOnID := args[1]
app := AppFromContext(cmd.Context())
dep := &models.Dependency{
IssueID: issueID,
DependsOnID: dependsOnID,
Type: models.DepBlocks,
}
if err := app.Issues.AddDependency(cmd.Context(), dep, "cli"); err != nil {
return fmt.Errorf("failed to add dependency: %s depends on %s: %w", issueID, dependsOnID, err)
}
cmd.Printf("Added dependency: %s depends on %s\n", issueID, dependsOnID)
return runDepViewCmd(cmd, []string{issueID})
}
func runDepRemoveCmd(cmd *cobra.Command, args []string) error {
issueID := args[0]
dependsOnID := args[1]
app := AppFromContext(cmd.Context())
if err := app.Issues.RemoveDependency(cmd.Context(), issueID, dependsOnID, "cli"); err != nil {
return fmt.Errorf("failed to remove dependency: %s depends on %s: %w", issueID, dependsOnID, err)
}
cmd.Printf("Removed dependency: %s no longer depends on %s\n", issueID, dependsOnID)
return runDepViewCmd(cmd, []string{issueID})
}

View File

@@ -23,7 +23,6 @@ var rootSuggestions = []prompt.Suggest{
var baseSuggestions = []prompt.Suggest{
{Text: "help", Description: "Show help information"},
{Text: "sprint", Description: "Manage sprints"},
{Text: "dep", Description: "Manage dependencies"},
{Text: "create", Description: "Create a new issue with title"},
{Text: "list", Description: "List all issues"},
{Text: "read", Description: "Read issue details by ID"},
@@ -42,7 +41,6 @@ var baseSuggestions = []prompt.Suggest{
{Text: "rm", Description: "Alias for delete command"},
{Text: "del", Description: "Alias for delete command"},
{Text: "get", Description: "Alias for read command"},
{Text: "dependencies", Description: "Alias for dep"},
}
// createFlags is a list of prompt suggestions for the create command flags.
@@ -125,14 +123,6 @@ var sprintSubcommands = []prompt.Suggest{
{Text: "del", Description: "Alias for delete subcommand"},
}
var depSubcommands = []prompt.Suggest{
{Text: "view", Description: "View dependencies of an issue"},
{Text: "show", Description: "Alias for view"},
{Text: "add", Description: "Add a dependency"},
{Text: "remove", Description: "Remove a dependency"},
{Text: "rm", Description: "Alias for remove"},
}
// statusValues is a list of prompt suggestions for status types
var statusValues = []prompt.Suggest{
{Text: "open", Description: "Open status"},
@@ -205,73 +195,6 @@ func commandSuggestions(words []string) []prompt.Suggest {
func flagSuggestions(cmd string, words []string, text string) []prompt.Suggest {
lastWord, prevWord := parseWords(words, text)
// Custom completion logic for `pm dep ...`
if cmd == "dep" {
switch len(words) {
case 1:
// `pm dep ` => next token could be a subcommand; default view is handled
// once the user types an issue id
if strings.HasSuffix(text, " ") {
return depSubcommands
}
return nil
case 2:
// either:
// - pm dep <issueID> (default view)
// - pm dep <subcommand>
if strings.HasSuffix(text, " ") {
sub := words[1]
switch sub {
case "view", "show", "add", "remove", "rm":
// After a valid subcommand we expect the issue id next.
return issueIDSuggestions("", true)
default:
// Treat it as an issue id already; no more suggestions.
return nil
}
}
// Typing the 2nd token: suggest subcommands and issue IDs that match.
suggests := make([]prompt.Suggest, 0, len(depSubcommands))
suggests = append(suggests, filterByPrefix(depSubcommands, lastWord)...)
//suggests = append(suggests, issueIDSuggestions(lastWord, true)...)
return suggests
case 3:
// If subcommand is `add/remove`, third token is the issue id.
// If subcommand is show/list, third token is the issue id.
sub := words[1]
switch sub {
case "add", "remove", "rm":
if strings.HasSuffix(text, " ") {
// `pm dep add <issueID> ` => next token is dependsOnID.
return issueIDSuggestions("", true)
}
return issueIDSuggestions(lastWord, true)
default:
// show/list/show => completing the issue id.
if strings.HasSuffix(text, " ") {
return nil
}
return issueIDSuggestions(lastWord, true)
}
case 4:
// add/remove/rm have a 4th token: dependsOnID.
sub := words[1]
if sub == "add" || sub == "remove" || sub == "rm" {
if strings.HasSuffix(text, " ") {
return nil
}
return issueIDSuggestions(lastWord, true)
}
return nil
}
return nil
}
if values := getFlagValues(prevWord); values != nil {
return filterByPrefix(values, lastWord)
}

View File

@@ -215,8 +215,8 @@ func helpBarConfig(view ViewKind) HelpBarConfig {
{Key: "n", Desc: "new sprint"},
{Key: "S", Desc: "select sprint"},
{Key: "pgup/pgdn", Desc: "page"},
{Key: "h/l", Desc: "column"},
{Key: "←/→", Desc: "move"},
{Key: "←/→", Desc: "column"},
{Key: "h/l", Desc: "move"},
{Key: "a", Desc: "add"},
{Key: "e/d/s/p/t/A/D", Desc: "edit"},
{Key: "x", Desc: "delete"},
@@ -225,8 +225,8 @@ func helpBarConfig(view ViewKind) HelpBarConfig {
},
FullItems: []HelpItem{
{Key: "v", Desc: "list view"},
{Key: "h/l", Desc: "switch column"},
{Key: "←/→", Desc: "move issue"},
{Key: "←/→", Desc: "switch column"},
{Key: "h/l", Desc: "move issue"},
{Key: "↑/k", Desc: "up"},
{Key: "↓/j", Desc: "down"},
{Key: "/", Desc: "search"},
@@ -240,7 +240,7 @@ func helpBarConfig(view ViewKind) HelpBarConfig {
{Key: "p", Desc: "change priority"},
{Key: "t", Desc: "change type"},
{Key: "A", Desc: "change assignee"},
{Key: "D", Desc: "manage dependencies"},
{Key: "D", Desc: "manage dependency"}, // dependencies -> dependency cuz dependencies added a newline
{Key: "S", Desc: "select sprint"},
{Key: "n", Desc: "new sprint"},
{Key: "q", Desc: "quit"},

View File

@@ -27,20 +27,20 @@ var defaultKanbanKeyMap = KeyMap{
key.WithHelp("v", "dashboard"),
),
MoveColumnLeft: key.NewBinding(
key.WithKeys("h"),
key.WithHelp("h", "prev column"),
key.WithKeys("left"),
key.WithHelp("", "prev column"),
),
MoveColumnRight: key.NewBinding(
key.WithKeys("l"),
key.WithHelp("l", "next column"),
key.WithKeys("right"),
key.WithHelp("", "next column"),
),
MoveIssueLeft: key.NewBinding(
key.WithKeys("left", "["),
key.WithHelp("/[", "move issue left"),
key.WithKeys("h", "["),
key.WithHelp("h/[", "move issue left"),
),
MoveIssueRight: key.NewBinding(
key.WithKeys("right", "]"),
key.WithHelp("/]", "move issue right"),
key.WithKeys("l", "]"),
key.WithHelp("l/]", "move issue right"),
),
AddComment: key.NewBinding(
key.WithKeys("c"),