Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/wish #33

Merged
merged 41 commits into from
Jul 9, 2024
Merged

Feat/wish #33

merged 41 commits into from
Jul 9, 2024

Conversation

FilippoTrotter
Copy link
Collaborator

@FilippoTrotter FilippoTrotter commented Jul 1, 2024

Summary by CodeRabbit

  • New Features

    • Introduced tgcom, a command-line tool for commenting and uncommenting code in various languages.
    • Added SSH server support for remote interactions with tgcom.
  • Enhancements

    • Improved terminal user interface (TUI) for file selection, mode selection, and action selection.
    • Functions for modifying files based on comments, with support for dry-run mode and terminal emulation.
  • Bug Fixes

    • Corrected directory navigation and file selection in the TUI.
    • Fixed validations for label inputs in the TUI.
  • Tests

    • Comprehensive tests added for file selection, label input, mode selection, and server functionality.

Copy link
Contributor

coderabbitai bot commented Jul 1, 2024

Walkthrough

Walkthrough

The changes to the project include the addition of a GitHub Actions workflow for a Go project, introducing SSH server functionality to start and manage remote interactions with tgcom, and creating a comprehensive command-line tool named tgcom for commenting and uncommenting code in various languages. Additionally, new dependencies are added, and several packages including commenter, modfile, server, and tui have been introduced or enhanced to support these features.

Changes

File/Group Change Summary
.github/workflows/go.yml Modified build command by removing the output flag specification.
cmd/server.go Added functions to start SSH server, execute remote commands via SSH, and support terminal emulation (PTY).
cmd/root.go Introduced tgcom CLI tool, including various functionalities for modifying code comments, file handling, and TUI.
go.mod Updated and added new package dependencies.
main.go Introduced a basic Go program to execute the Execute() function from the cmd package.
utils/commenter/commenter.go Added functions to add, remove, and toggle comments in text supporting various languages.
utils/modfile/modfile.go Introduced functionalities to modify files based on comments, including file handling and configuration settings.
utils/server/server.go Added StartServer function for initializing SSH server capabilities and handling commands and signals.
utils/tui/...model.go Created TUI Model struct for managing user interface states and interactions for file selection and modification.
utils/tui/...file_test.go Added tests for FilesSelector struct covering file selection and directory navigation.
utils/tui/...text.go Introduced LabelInput struct to manage label inputs in text-based UI with methods for initialization and updates.
utils/tui/...option.go Added ModeSelector struct and methods for handling mode selection in the user interface interactively.
utils/tui/...selectorutils.go Introduced utility functions for file selection and directory navigation.
utils/tui/...text_test.go Added tests to validate LabelInput struct functionality in text-based user interface.
utils/tui/...selectorutils_test.go Introduced tests for directory utilities to validate directory navigation and path manipulation.
utils/tui/model_test.go Added tests for TUI Model struct covering initialization, updates, file changes, and rendering views.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 0ad2721 and 077e840.

Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
Files selected for processing (15)
  • .github/workflows/go.yml (1 hunks)
  • cmd/root.go (1 hunks)
  • cmd/server.go (1 hunks)
  • go.mod (1 hunks)
  • main.go (1 hunks)
  • utils/commenter/commenter.go (1 hunks)
  • utils/commenter/commenter_test.go (1 hunks)
  • utils/modfile/modfile.go (1 hunks)
  • utils/modfile/modfile_test.go (1 hunks)
  • utils/server/server.go (1 hunks)
  • utils/tui/model.go (1 hunks)
  • utils/tui/modelutils/file.go (1 hunks)
  • utils/tui/modelutils/option.go (1 hunks)
  • utils/tui/modelutils/selectorutils.go (1 hunks)
  • utils/tui/modelutils/text.go (1 hunks)
Files not summarized due to errors (11)
  • utils/server/server.go: Error: Server error. Please try again later.
  • cmd/root.go: Error: Server error. Please try again later.
  • go.mod: Error: Server error. Please try again later.
  • utils/tui/modelutils/selectorutils.go: Error: Server error. Please try again later.
  • utils/tui/modelutils/option.go: Error: Server error. Please try again later.
  • utils/tui/modelutils/text.go: Error: Server error. Please try again later.
  • utils/tui/model.go: Error: Server error. Please try again later.
  • utils/commenter/commenter_test.go: Error: Server error. Please try again later.
  • cmd/server.go: Error: Server error. Please try again later.
  • main.go: Error: Server error. Please try again later.
  • utils/commenter/commenter.go: Error: Server error. Please try again later.
Files not reviewed due to errors (6)
  • main.go (Error: Server error. Please try again later.)
  • utils/commenter/commenter_test.go (Error: Server error. Please try again later.)
  • utils/commenter/commenter.go (Error: Server error. Please try again later.)
  • utils/tui/modelutils/option.go (Error: Server error. Please try again later.)
  • cmd/root.go (no review received)
  • utils/tui/model.go (no review received)
Files skipped from review due to trivial changes (1)
  • .github/workflows/go.yml
Additional comments not posted (20)
go.mod (1)

5-15: Review of module dependencies: All dependencies appear correctly specified.

The dependencies listed are appropriate for the project, and there are no signs of unnecessary or outdated packages.

Also applies to: 17-49

cmd/server.go (1)

20-33: Review of serverCmd CLI command: Correctly defined and initialized.

The serverCmd is well-defined with appropriate descriptions and is correctly registered within the command tree.

utils/tui/modelutils/text.go (1)

16-16: Add documentation for the new IsLabel field.

The new field IsLabel has been added to the LabelInput struct to distinguish between labels and line numbers. It's a good practice to add a comment explaining the purpose and usage of this field for better code readability and maintenance.

utils/modfile/modfile.go (9)

1-14: LGTM!

The package declaration and imports look good.


16-25: LGTM!

The Config struct is well-defined.


147-152: LGTM!

The shouldProcessLine function is well-implemented.


257-276: LGTM!

The createBackup function is well-implemented.


278-283: LGTM!

The restoreBackup function is well-implemented.


285-308: LGTM!

The findLines function is well-implemented.


310-381: LGTM!

The selectCommentChars function is well-implemented.


383-413: LGTM!

The CommentChars map is comprehensive and well-defined.


27-42: Add a default case to handle unsupported actions.

The setModFunc function should handle unsupported actions explicitly.

-  return nil, fmt.Errorf("invalid action. Please provide 'comment', 'uncomment', or 'toggle'")
+  default:
+    return nil, fmt.Errorf("invalid action: %s. Please provide 'comment', 'uncomment', or 'toggle'", action)

Likely invalid or redundant comment.

utils/modfile/modfile_test.go (8)

10-98: LGTM!

The TestWriteChanges function is well-implemented and covers various scenarios.


100-183: LGTM!

The TestPrintChanges function is well-implemented and covers various scenarios.


187-345: LGTM!

The TestChangeFile function is well-implemented and covers various scenarios.


349-362: LGTM!

The TestCreateBackup function is well-implemented.


364-380: LGTM!

The TestRestoreBackup function is well-implemented.


382-401: LGTM!

The TestSelectCommentChars function is well-implemented and covers various scenarios.


402-424: LGTM!

The TestFindLines function is well-implemented and covers various scenarios.


426-452: LGTM!

The utility functions are well-implemented and aid in setting up and cleaning up test files.

utils/tui/modelutils/selectorutils.go Outdated Show resolved Hide resolved
Comment on lines +35 to +114
func executeRemoteCommand(remotePath string) {
parts := strings.SplitN(remotePath, "@", 2)
if len(parts) != 2 {
fmt.Println("Invalid format. Usage: tgcom -w user@remote:/path/folder")
os.Exit(1)
}

userHost := parts[0]
pathParts := strings.SplitN(parts[1], ":", 2)
if len(pathParts) != 2 {
fmt.Println("Invalid format. Usage: tgcom -w user@remote:/path/folder")
os.Exit(1)
}

host := pathParts[0]
dir := pathParts[1]

sshCmd := "ssh"
sshArgs := []string{"-t", "-p", "2222", userHost + "@" + host, "tgcom", dir}

// Start SSH command with PTY
if err := startSSHWithPTY(sshCmd, sshArgs); err != nil {
log.Fatalf("Error starting SSH with PTY: %v", err)
}
}

func startSSHWithPTY(cmd string, args []string) error {
// Create SSH command
sshCommand := exec.Command(cmd, args...)

// Start PTY
ptmx, err := pty.Start(sshCommand)
if err != nil {
return fmt.Errorf("failed to start PTY: %w", err)
}
defer ptmx.Close()

// Set terminal attributes
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("failed to make terminal raw: %w", err)
}
defer term.Restore(int(os.Stdin.Fd()), oldState)

// Resize PTY to current terminal size
if err := resizePTY(ptmx); err != nil {
return fmt.Errorf("failed to resize PTY: %w", err)
}

// Forward input to PTY
go func() {
_, _ = io.Copy(ptmx, os.Stdin)
}()

// Forward output from PTY
go func() {
_, _ = io.Copy(os.Stdout, ptmx)
}()

// Handle PTY signals and resize
go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
for range ch {
if err := resizePTY(ptmx); err != nil {
log.Printf("Error resizing PTY: %v", err)
}
}
}()

// Wait for SSH command to finish
if err := sshCommand.Wait(); err != nil {
return fmt.Errorf("SSH command failed: %w", err)
}

// Wait a bit before exiting to ensure all output is processed
time.Sleep(100 * time.Millisecond)

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of SSH command execution functions: Enhance error handling and resource management.

  1. Resource Management: Ensure that all resources like PTY and terminal states are properly managed. Consider adding more robust cleanup in error scenarios.
  2. Error Handling: The error handling could be more descriptive, providing more context about the failures.

Refactor the error handling to provide more detailed error messages and ensure resources are cleaned up even when errors occur.

utils/tui/modelutils/text.go Outdated Show resolved Hide resolved
utils/tui/modelutils/file.go Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Outside diff range and nitpick comments (2)
cmd/root.go (2)

Line range hint 154-191: Add error handling for writer flush operation.

The function lacks error handling for the writer flush operation at the end.

-  return writer.Flush()
+  if err := writer.Flush(); err != nil {
+    return fmt.Errorf("failed to flush writer: %w", err)
+  }
+  return nil

Line range hint 168-199: Add error handling for writer flush operation in writeChanges.

Ensure that the writeChanges function includes error handling for the writer flush operation.

-  return writer.Flush()
+  if err := writer.Flush(); err != nil {
+    return fmt.Errorf("failed to flush writer: %w", err)
+  }
+  return nil
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 0ad2721 and 077e840.

Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
Files selected for processing (15)
  • .github/workflows/go.yml (1 hunks)
  • cmd/root.go (1 hunks)
  • cmd/server.go (1 hunks)
  • go.mod (1 hunks)
  • main.go (1 hunks)
  • utils/commenter/commenter.go (1 hunks)
  • utils/commenter/commenter_test.go (1 hunks)
  • utils/modfile/modfile.go (1 hunks)
  • utils/modfile/modfile_test.go (1 hunks)
  • utils/server/server.go (1 hunks)
  • utils/tui/model.go (1 hunks)
  • utils/tui/modelutils/file.go (1 hunks)
  • utils/tui/modelutils/option.go (1 hunks)
  • utils/tui/modelutils/selectorutils.go (1 hunks)
  • utils/tui/modelutils/text.go (1 hunks)
Files not summarized due to errors (5)
  • main.go: Error: Server error. Please try again later.
  • utils/tui/modelutils/file.go: Error: Server error. Please try again later.
  • utils/server/server.go: Error: Server error. Please try again later.
  • cmd/root.go: Error: Server error. Please try again later.
  • utils/tui/modelutils/option.go: Error: Server error. Please try again later.
Files not reviewed due to errors (1)
  • utils/modfile/modfile_test.go (no review received)
Files skipped from review due to trivial changes (3)
  • .github/workflows/go.yml
  • go.mod
  • utils/commenter/commenter_test.go
Additional context used
golangci-lint
utils/commenter/commenter.go

[warning] 61-61: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)

utils/tui/modelutils/option.go

[warning] 63-63: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


[warning] 5-5: exported: exported type ModeSelector should have comment or be unexported

(revive)


90-90: unnecessary trailing newline

(whitespace)


65-65: unnecessary leading newline

(whitespace)

utils/tui/modelutils/file.go

[warning] 11-11: exported: exported type FilesSelector should have comment or be unexported

(revive)


[warning] 23-23: exported: exported function InitialModel should have comment or be unexported

(revive)

utils/tui/model.go

142-142: unnecessary trailing newline

(whitespace)


84-84: ineffectual assignment to counter

(ineffassign)


118-118: ineffectual assignment to counter

(ineffassign)

utils/modfile/modfile.go

60-60: Error return value of file.Close is not checked

(errcheck)


103-103: Error return value of tmpFile.Close is not checked

(errcheck)


108-108: Error return value of tmpFile.Close is not checked

(errcheck)


109-109: Error return value of os.Remove is not checked

(errcheck)


116-116: Error return value of tmpFile.Close is not checked

(errcheck)


117-117: Error return value of os.Remove is not checked

(errcheck)


123-123: Error return value of tmpFile.Close is not checked

(errcheck)


124-124: Error return value of os.Remove is not checked

(errcheck)


130-130: Error return value of os.Remove is not checked

(errcheck)


141-141: Error return value of os.Remove is not checked

(errcheck)


[warning] 300-300: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


41-41: unnecessary trailing newline

(whitespace)


198-198: unnecessary leading newline

(whitespace)


230-230: unnecessary leading newline

(whitespace)


241-241: unnecessary trailing newline

(whitespace)

utils/modfile/modfile_test.go

[medium] 373-373: G306: Expect WriteFile permissions to be 0600 or less

(gosec)


[warning] 43-43: unused-parameter: parameter 'commentChars' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 54-54: unused-parameter: parameter 'commentChars' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 133-133: unused-parameter: parameter 'commentChars' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 144-144: unused-parameter: parameter 'commentChars' seems to be unused, consider removing or renaming it as _

(revive)


187-187: unnecessary leading newline

(whitespace)


347-347: unnecessary trailing newline

(whitespace)

Additional comments not posted (42)
main.go (1)

1-9: LGTM!

The main.go file correctly initializes the main command execution for the application.

utils/tui/modelutils/option.go (2)

14-21: LGTM!

The NewModeSelector function correctly initializes a new ModeSelector instance.


23-25: LGTM!

The Init function correctly initializes the ModeSelector instance.

utils/tui/modelutils/selectorutils.go (7)

11-18: LGTM!

The Contains function is implemented correctly.


20-28: LGTM!

The Remove function is implemented correctly.


30-36: LGTM!

The IsDirectory function is implemented correctly.


38-51: LGTM!

The GetParentDirectory function is implemented correctly.


53-65: LGTM!

The GetPathOfEntry function is implemented correctly.


67-93: LGTM!

The moveToNextDir function is implemented correctly.


95-127: LGTM!

The moveToPreviousDir function is implemented correctly.

cmd/server.go (5)

20-28: LGTM!

The serverCmd Cobra command is implemented correctly.


30-33: LGTM!

The init function is implemented correctly.


35-59: LGTM!

The executeRemoteCommand function is implemented correctly.


61-113: LGTM!

The startSSHWithPTY function is implemented correctly.


116-125: LGTM!

The resizePTY function is implemented correctly.

utils/tui/modelutils/text.go (6)

21-28: LGTM!

The NewLabelInput function is implemented correctly.


30-32: LGTM!

The LabelInput.Init function is implemented correctly.


34-60: LGTM!

The LabelInput.Update function is implemented correctly.


62-80: LGTM!

The LabelInput.View function is implemented correctly.


83-87: LGTM!

The StartTicker function is implemented correctly.


91-133: LGTM!

The validateInput function is implemented correctly.

utils/tui/modelutils/file.go (5)

23-50: LGTM!

The InitialModel function is implemented correctly.

Tools
golangci-lint

[warning] 23-23: exported: exported function InitialModel should have comment or be unexported

(revive)


52-54: LGTM!

The FilesSelector.Init function is implemented correctly.


56-102: LGTM!

The FilesSelector.Update function is implemented correctly.


104-140: LGTM!

The FilesSelector.View function is implemented correctly.


142-163: LGTM!

The paint function is implemented correctly.

cmd/root.go (7)

40-45: LGTM!

The code changes are approved.


76-84: LGTM!

The code changes are approved.


129-157: LGTM!

The code changes are approved.


159-173: LGTM!

The code changes are approved.


147-152: LGTM!

The code changes are approved.


20-37: Verify the existence and correctness of executeRemoteCommand.

The executeRemoteCommand function is invoked but not defined in this file. Ensure it exists and is correctly implemented.

Verification successful

The executeRemoteCommand function is correctly implemented and exists in cmd/server.go.

  • cmd/server.go:35-59: The function splits the remotePath string, validates its format, constructs SSH command arguments, and starts an SSH session with a pseudo-terminal.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the existence and correctness of `executeRemoteCommand`.

# Test: Search for the function definition. Expect: Function definition exists.
rg --type go --pcre2 'func executeRemoteCommand'

Length of output: 111



Script:

#!/bin/bash
# Description: Retrieve the implementation of `executeRemoteCommand` function for review.

# Extract the function implementation from cmd/server.go
ast-grep --lang go --pattern 'func executeRemoteCommand($_) { $$$ }' cmd/server.go

Length of output: 1187


47-74: Verify the existence and correctness of serverCmd.

The serverCmd variable is added to the root command but not defined in this file. Ensure it exists and is correctly implemented.

Verification successful

The serverCmd is correctly implemented and the initial review comment is accurate.

  • The serverCmd variable is defined in cmd/server.go and correctly set up as a cobra.Command.
  • The Run function in serverCmd calls server.StartServer(), which is defined in utils/server/server.go.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the existence and correctness of `serverCmd`.

# Test: Search for the variable definition. Expect: Variable definition exists.
rg --type go --pcre2 'var serverCmd'

Length of output: 84



Script:

#!/bin/bash
# Description: Extract and verify the implementation of `serverCmd` in `cmd/server.go`.

# Extract the implementation of `serverCmd`.
ast-grep --lang go --pattern $'var serverCmd = &cobra.Command{
  $$$
}' cmd/server.go

Length of output: 457



Script:

#!/bin/bash
# Description: Verify the existence and correctness of `server.StartServer`.

# Test: Search for the function definition. Expect: Function definition exists.
rg --type go --pcre2 'func StartServer'

Length of output: 85

utils/tui/model.go (5)

13-29: LGTM!

The code changes are approved.


31-34: LGTM!

The code changes are approved.


146-165: LGTM!

The code changes are approved.


201-216: LGTM!

The code changes are approved.


168-199: Add error handling for writer flush operation in writeChanges.

Ensure that the writeChanges function includes error handling for the writer flush operation.

-  return writer.Flush()
+  if err := writer.Flush(); err != nil {
+    return fmt.Errorf("failed to flush writer: %w", err)
+  }
+  return nil

Likely invalid or redundant comment.

utils/modfile/modfile.go (4)

16-25: LGTM!

The code changes are approved.


27-42: LGTM!

The code changes are approved.

Tools
golangci-lint

41-41: unnecessary trailing newline

(whitespace)


147-152: LGTM!

The code changes are approved.


193-223: Remove unnecessary newlines.

There are some unnecessary leading and trailing newlines in the printChanges function.

-  fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
+  fmt.Printf("%d: %s -> %s", currentLine, lineContent, modified)

Likely invalid or redundant comment.

Tools
golangci-lint

198-198: unnecessary leading newline

(whitespace)

utils/commenter/commenter.go Outdated Show resolved Hide resolved
utils/commenter/commenter.go Outdated Show resolved Hide resolved
cmd/root.go Outdated Show resolved Hide resolved
utils/tui/model.go Outdated Show resolved Hide resolved
utils/commenter/commenter.go Outdated Show resolved Hide resolved
Comment on lines 52 to 90
func (m ModeSelector) View() string {
if len(m.Choices) == 2 {
s := paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
return s
} else {
s := ""
switch m.Speed {

case "Slow mode":
s += paint("silver").Render("Select action for file: "+m.File) + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}

case "Fast mode":
s += paint("silver").Render("Select action:") + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
}
return s + paint("silver").Render("\n 'q' to quit 'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix unnecessary leading and trailing newlines.

The function should be refactored to remove unnecessary leading and trailing newlines.

-	if len(m.Choices) == 2 {
-		s := paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
-		for i, choice := range m.Choices {
-			cursor := " "
-			if m.cursor == i {
-				cursor = ">"
-			}
-			s += cursor + " " + choice + "\n"
-		}
-		return s
-	} else {
-		s := ""
-		switch m.Speed {
-		case "Slow mode":
-			s += paint("silver").Render("Select action for file: "+m.File) + "\n\n"
-			for i, choice := range m.Choices {
-				cursor := " "
-				if m.cursor == i {
-					cursor = ">"
-				}
-				s += cursor + " " + choice + "\n"
-			}
-		case "Fast mode":
-			s += paint("silver").Render("Select action:") + "\n\n"
-			for i, choice := range m.Choices {
-				cursor := " "
-				if m.cursor == i {
-					cursor = ">"
-				}
-				s += cursor + " " + choice + "\n"
-			}
-		}
-		return s + paint("silver").Render("\n 'q' to quit     'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
-	}
+	if len(m.Choices) == 2 {
+		s := paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
+		for i, choice := range m.Choices {
+			cursor := " "
+			if m.cursor == i {
+				cursor = ">"
+			}
+			s += cursor + " " + choice + "\n"
+		}
+		return s
+	}
+	s := ""
+	switch m.Speed {
+	case "Slow mode":
+		s += paint("silver").Render("Select action for file: "+m.File) + "\n\n"
+		for i, choice := range m.Choices {
+			cursor := " "
+			if m.cursor == i {
+				cursor = ">"
+			}
+			s += cursor + " " + choice + "\n"
+		}
+	case "Fast mode":
+		s += paint("silver").Render("Select action:") + "\n\n"
+		for i, choice := range m.Choices {
+			cursor := " "
+			if m.cursor == i {
+				cursor = ">"
+			}
+			s += cursor + " " + choice + "\n"
+		}
+	}
+	return s + paint("silver").Render("\n 'q' to quit     'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m ModeSelector) View() string {
if len(m.Choices) == 2 {
s := paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
return s
} else {
s := ""
switch m.Speed {
case "Slow mode":
s += paint("silver").Render("Select action for file: "+m.File) + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
case "Fast mode":
s += paint("silver").Render("Select action:") + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
}
return s + paint("silver").Render("\n 'q' to quit 'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}
}
func (m ModeSelector) View() string {
if len(m.Choices) == 2 {
s := paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
return s
}
s := ""
switch m.Speed {
case "Slow mode":
s += paint("silver").Render("Select action for file: "+m.File) + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
case "Fast mode":
s += paint("silver").Render("Select action:") + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
}
return s + paint("silver").Render("\n 'q' to quit 'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}
Tools
golangci-lint

[warning] 63-63: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


90-90: unnecessary trailing newline

(whitespace)


65-65: unnecessary leading newline

(whitespace)

utils/server/server.go Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 077e840 and d729dc3.

Files selected for processing (2)
  • utils/tui/modelutils/file.go (1 hunks)
  • utils/tui/modelutils/selectorutils.go (1 hunks)
Additional context used
golangci-lint
utils/tui/modelutils/selectorutils.go

[warning] 10-10: exported: exported function Contains should have comment or be unexported

(revive)

utils/tui/modelutils/file.go

[warning] 11-11: exported: exported type FilesSelector should have comment or be unexported

(revive)


[warning] 23-23: exported: exported function InitialModel should have comment or be unexported

(revive)


[warning] 52-52: exported: exported method FilesSelector.Init should have comment or be unexported

(revive)


[warning] 56-56: exported: exported method FilesSelector.Update should have comment or be unexported

(revive)


[warning] 112-112: exported: exported method FilesSelector.View should have comment or be unexported

(revive)

Comment on lines 19 to 27
func Remove(slice []string, target string) []string {
result := make([]string, 0, len(slice))
for _, item := range slice {
if item != target {
result = append(result, item)
}
}
return result
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function Remove.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // Remove removes a specific string from a slice.
  func Remove(slice []string, target string) []string {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func Remove(slice []string, target string) []string {
result := make([]string, 0, len(slice))
for _, item := range slice {
if item != target {
result = append(result, item)
}
}
return result
}
// Remove removes a specific string from a slice.
func Remove(slice []string, target string) []string {
result := make([]string, 0, len(slice))
for _, item := range slice {
if item != target {
result = append(result, item)
}
}
return result
}

Comment on lines 29 to 35
func IsDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function IsDirectory.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // IsDirectory checks if a given path is a directory.
  func IsDirectory(path string) (bool, error) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func IsDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), nil
}
// IsDirectory checks if a given path is a directory.
func IsDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), nil
}

Comment on lines 52 to 64
func GetPathOfEntry(entry fs.DirEntry, baseDir string) (string, error) {
_, err := entry.Info()
if err != nil {
return "", err
}

absPath, err := filepath.Abs(filepath.Join(baseDir, entry.Name()))
if err != nil {
return "", err
}

return absPath, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function GetPathOfEntry.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // GetPathOfEntry gets the absolute path of an entry in a specified base directory.
  func GetPathOfEntry(entry fs.DirEntry, baseDir string) (string, error) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func GetPathOfEntry(entry fs.DirEntry, baseDir string) (string, error) {
_, err := entry.Info()
if err != nil {
return "", err
}
absPath, err := filepath.Abs(filepath.Join(baseDir, entry.Name()))
if err != nil {
return "", err
}
return absPath, nil
}
// GetPathOfEntry gets the absolute path of an entry in a specified base directory.
func GetPathOfEntry(entry fs.DirEntry, baseDir string) (string, error) {
_, err := entry.Info()
if err != nil {
return "", err
}
absPath, err := filepath.Abs(filepath.Join(baseDir, entry.Name()))
if err != nil {
return "", err
}
return absPath, nil
}

Comment on lines 37 to 50
func GetParentDirectory(directoryPath string) (string, error) {
normalizedPath := filepath.Clean(directoryPath)

if normalizedPath == "/" {
return directoryPath, nil
}

parentDir := filepath.Dir(directoryPath)
if parentDir == directoryPath {
return "", fmt.Errorf("the given path '%s' is a root directory or invalid", directoryPath)
}

return parentDir, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function GetParentDirectory.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // GetParentDirectory gets the parent directory of a given path.
  func GetParentDirectory(directoryPath string) (string, error) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func GetParentDirectory(directoryPath string) (string, error) {
normalizedPath := filepath.Clean(directoryPath)
if normalizedPath == "/" {
return directoryPath, nil
}
parentDir := filepath.Dir(directoryPath)
if parentDir == directoryPath {
return "", fmt.Errorf("the given path '%s' is a root directory or invalid", directoryPath)
}
return parentDir, nil
}
// GetParentDirectory gets the parent directory of a given path.
func GetParentDirectory(directoryPath string) (string, error) {
normalizedPath := filepath.Clean(directoryPath)
if normalizedPath == "/" {
return directoryPath, nil
}
parentDir := filepath.Dir(directoryPath)
if parentDir == directoryPath {
return "", fmt.Errorf("the given path '%s' is a root directory or invalid", directoryPath)
}
return parentDir, nil
}

Comment on lines 10 to 17
func Contains(slice []string, str string) bool {
for _, item := range slice {
if item == str {
return true
}
}
return false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function Contains.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // Contains checks if a slice contains a specific string.
  func Contains(slice []string, str string) bool {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func Contains(slice []string, str string) bool {
for _, item := range slice {
if item == str {
return true
}
}
return false
}
// Contains checks if a slice contains a specific string.
func Contains(slice []string, str string) bool {
for _, item := range slice {
if item == str {
return true
}
}
return false
}
Tools
golangci-lint

[warning] 10-10: exported: exported function Contains should have comment or be unexported

(revive)

Comment on lines 52 to 54
func (m FilesSelector) Init() tea.Cmd {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function Init.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // Init initializes the FilesSelector struct for the TUI.
  func (m FilesSelector) Init() tea.Cmd {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m FilesSelector) Init() tea.Cmd {
return nil
}
// Init initializes the FilesSelector struct for the TUI.
func (m FilesSelector) Init() tea.Cmd {
return nil
}
Tools
golangci-lint

[warning] 52-52: exported: exported method FilesSelector.Init should have comment or be unexported

(revive)

Comment on lines 11 to 21
type FilesSelector struct {
CurrentDir string
FilesAndDir []string
SelectedFilesAndDir map[int]bool
FilesPath []string
cursor int
scrollOffset int
Done bool
WindowHeight int
Error error
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported struct FilesSelector.

According to Go conventions, exported structs should have a comment explaining their purpose.

+ // FilesSelector represents the main application model for the TUI.
  type FilesSelector struct {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type FilesSelector struct {
CurrentDir string
FilesAndDir []string
SelectedFilesAndDir map[int]bool
FilesPath []string
cursor int
scrollOffset int
Done bool
WindowHeight int
Error error
}
// FilesSelector represents the main application model for the TUI.
type FilesSelector struct {
CurrentDir string
FilesAndDir []string
SelectedFilesAndDir map[int]bool
FilesPath []string
cursor int
scrollOffset int
Done bool
WindowHeight int
Error error
}
Tools
golangci-lint

[warning] 11-11: exported: exported type FilesSelector should have comment or be unexported

(revive)

Comment on lines 150 to 171
func paint(color string) lipgloss.Style {
switch color {
case "lime":
lime := lipgloss.Color("#00FF00")
return lipgloss.NewStyle().Foreground(lime)
case "blue":
blue := lipgloss.Color("#0000FF")
return lipgloss.NewStyle().Foreground(blue)
case "green":
green := lipgloss.Color("#008000")
return lipgloss.NewStyle().Foreground(green)
case "red":
red := lipgloss.Color("#FF0000")
return lipgloss.NewStyle().Foreground(red)
case "silver":
silver := lipgloss.Color("#C0C0C0")
return lipgloss.NewStyle().Foreground(silver)
default:
white := lipgloss.Color("#FFFFFF")
return lipgloss.NewStyle().Foreground(white)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function paint.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // paint returns a lipgloss style based on the input color.
  func paint(color string) lipgloss.Style {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func paint(color string) lipgloss.Style {
switch color {
case "lime":
lime := lipgloss.Color("#00FF00")
return lipgloss.NewStyle().Foreground(lime)
case "blue":
blue := lipgloss.Color("#0000FF")
return lipgloss.NewStyle().Foreground(blue)
case "green":
green := lipgloss.Color("#008000")
return lipgloss.NewStyle().Foreground(green)
case "red":
red := lipgloss.Color("#FF0000")
return lipgloss.NewStyle().Foreground(red)
case "silver":
silver := lipgloss.Color("#C0C0C0")
return lipgloss.NewStyle().Foreground(silver)
default:
white := lipgloss.Color("#FFFFFF")
return lipgloss.NewStyle().Foreground(white)
}
}
// paint returns a lipgloss style based on the input color.
func paint(color string) lipgloss.Style {
switch color {
case "lime":
lime := lipgloss.Color("#00FF00")
return lipgloss.NewStyle().Foreground(lime)
case "blue":
blue := lipgloss.Color("#0000FF")
return lipgloss.NewStyle().Foreground(blue)
case "green":
green := lipgloss.Color("#008000")
return lipgloss.NewStyle().Foreground(green)
case "red":
red := lipgloss.Color("#FF0000")
return lipgloss.NewStyle().Foreground(red)
case "silver":
silver := lipgloss.Color("#C0C0C0")
return lipgloss.NewStyle().Foreground(silver)
default:
white := lipgloss.Color("#FFFFFF")
return lipgloss.NewStyle().Foreground(white)
}
}

Comment on lines 56 to 110
func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.Error != nil {
return m, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
if m.cursor < m.scrollOffset {
m.scrollOffset--
}
}
case "down":
if m.cursor < len(m.FilesAndDir)-1 {
m.cursor++
if m.cursor >= m.scrollOffset+m.WindowHeight {
m.scrollOffset++
}
}
case "enter":
checkDir, err := IsDirectory(m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
if checkDir {
err := moveToNextDir(&m, m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
} else {
if Contains(m.FilesPath, m.FilesAndDir[m.cursor]) {
m.FilesPath = Remove(m.FilesPath, m.FilesAndDir[m.cursor])
} else {
m.FilesPath = append(m.FilesPath, m.FilesAndDir[m.cursor])
}
m.SelectedFilesAndDir[m.cursor] = !m.SelectedFilesAndDir[m.cursor]
}
case "esc":
err := moveToPreviousDir(&m)
if err != nil {
m.Error = fmt.Errorf("error moving back: %w", err)
return m, tea.Quit
}
case "x":
m.Done = true
}
}
return m, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function Update.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // Update updates the state of the FilesSelector struct based on user input.
  func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.Error != nil {
return m, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
if m.cursor < m.scrollOffset {
m.scrollOffset--
}
}
case "down":
if m.cursor < len(m.FilesAndDir)-1 {
m.cursor++
if m.cursor >= m.scrollOffset+m.WindowHeight {
m.scrollOffset++
}
}
case "enter":
checkDir, err := IsDirectory(m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
if checkDir {
err := moveToNextDir(&m, m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
} else {
if Contains(m.FilesPath, m.FilesAndDir[m.cursor]) {
m.FilesPath = Remove(m.FilesPath, m.FilesAndDir[m.cursor])
} else {
m.FilesPath = append(m.FilesPath, m.FilesAndDir[m.cursor])
}
m.SelectedFilesAndDir[m.cursor] = !m.SelectedFilesAndDir[m.cursor]
}
case "esc":
err := moveToPreviousDir(&m)
if err != nil {
m.Error = fmt.Errorf("error moving back: %w", err)
return m, tea.Quit
}
case "x":
m.Done = true
}
}
return m, nil
}
// Update updates the state of the FilesSelector struct based on user input.
func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.Error != nil {
return m, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
if m.cursor < m.scrollOffset {
m.scrollOffset--
}
}
case "down":
if m.cursor < len(m.FilesAndDir)-1 {
m.cursor++
if m.cursor >= m.scrollOffset+m.WindowHeight {
m.scrollOffset++
}
}
case "enter":
checkDir, err := IsDirectory(m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
if checkDir {
err := moveToNextDir(&m, m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
} else {
if Contains(m.FilesPath, m.FilesAndDir[m.cursor]) {
m.FilesPath = Remove(m.FilesPath, m.FilesAndDir[m.cursor])
} else {
m.FilesPath = append(m.FilesPath, m.FilesAndDir[m.cursor])
}
m.SelectedFilesAndDir[m.cursor] = !m.SelectedFilesAndDir[m.cursor]
}
case "esc":
err := moveToPreviousDir(&m)
if err != nil {
m.Error = fmt.Errorf("error moving back: %w", err)
return m, tea.Quit
}
case "x":
m.Done = true
}
}
return m, nil
}
Tools
golangci-lint

[warning] 56-56: exported: exported method FilesSelector.Update should have comment or be unexported

(revive)

Comment on lines 112 to 148
func (m FilesSelector) View() string {
if m.Error != nil {
return paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}

s := paint("silver").Render("\n Select the files you want to modify...") + "\n"
s += paint("silver").Render("\n Selected files till now:") + "\n"
for i := 0; i < len(m.FilesPath); i++ {
s += fmt.Sprintf(" %s\n", paint("green").Render(m.FilesPath[i]))
}
s += "\n"

for i := m.scrollOffset; i < m.scrollOffset+m.WindowHeight && i < len(m.FilesAndDir); i++ {
choice := m.FilesAndDir[i]
checkDir, err := IsDirectory(choice)
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
if checkDir {
choice = paint("blue").Render("❒ " + choice)
} else if Contains(m.FilesPath, choice) {
choice = paint("lime").Render("❒ " + choice)
} else {
choice = paint("silver").Render("❒ " + choice)
}

cursor := " "
if m.cursor == i {
cursor = paint("red").Render(" ➪")
}

s += fmt.Sprintf("%s %s\n", cursor, choice)
}
s += paint("silver").Render("\n 'q' to quit 'esc' to move to parent directory\n '↑' to go up 'x' to modify selected files\n '↓' to go down 'enter' to select pointed file/move to pointed sub folder")
return s
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function View.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // View renders the view of the FilesSelector struct for the TUI.
  func (m FilesSelector) View() string {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m FilesSelector) View() string {
if m.Error != nil {
return paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
s := paint("silver").Render("\n Select the files you want to modify...") + "\n"
s += paint("silver").Render("\n Selected files till now:") + "\n"
for i := 0; i < len(m.FilesPath); i++ {
s += fmt.Sprintf(" %s\n", paint("green").Render(m.FilesPath[i]))
}
s += "\n"
for i := m.scrollOffset; i < m.scrollOffset+m.WindowHeight && i < len(m.FilesAndDir); i++ {
choice := m.FilesAndDir[i]
checkDir, err := IsDirectory(choice)
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
if checkDir {
choice = paint("blue").Render("❒ " + choice)
} else if Contains(m.FilesPath, choice) {
choice = paint("lime").Render("❒ " + choice)
} else {
choice = paint("silver").Render("❒ " + choice)
}
cursor := " "
if m.cursor == i {
cursor = paint("red").Render(" ➪")
}
s += fmt.Sprintf("%s %s\n", cursor, choice)
}
s += paint("silver").Render("\n 'q' to quit 'esc' to move to parent directory\n '↑' to go up 'x' to modify selected files\n '↓' to go down 'enter' to select pointed file/move to pointed sub folder")
return s
}
// View renders the view of the FilesSelector struct for the TUI.
func (m FilesSelector) View() string {
if m.Error != nil {
return paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
s := paint("silver").Render("\n Select the files you want to modify...") + "\n"
s += paint("silver").Render("\n Selected files till now:") + "\n"
for i := 0; i < len(m.FilesPath); i++ {
s += fmt.Sprintf(" %s\n", paint("green").Render(m.FilesPath[i]))
}
s += "\n"
for i := m.scrollOffset; i < m.scrollOffset+m.WindowHeight && i < len(m.FilesAndDir); i++ {
choice := m.FilesAndDir[i]
checkDir, err := IsDirectory(choice)
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
if checkDir {
choice = paint("blue").Render("❒ " + choice)
} else if Contains(m.FilesPath, choice) {
choice = paint("lime").Render("❒ " + choice)
} else {
choice = paint("silver").Render("❒ " + choice)
}
cursor := " "
if m.cursor == i {
cursor = paint("red").Render(" ➪")
}
s += fmt.Sprintf("%s %s\n", cursor, choice)
}
s += paint("silver").Render("\n 'q' to quit 'esc' to move to parent directory\n '↑' to go up 'x' to modify selected files\n '↓' to go down 'enter' to select pointed file/move to pointed sub folder")
return s
}
Tools
golangci-lint

[warning] 112-112: exported: exported method FilesSelector.View should have comment or be unexported

(revive)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 0bba042 and 7be875d.

Files selected for processing (5)
  • main.go (1 hunks)
  • utils/tui/model.go (1 hunks)
  • utils/tui/modelutils/file.go (1 hunks)
  • utils/tui/modelutils/option.go (1 hunks)
  • utils/tui/modelutils/text.go (1 hunks)
Files skipped from review due to trivial changes (1)
  • main.go
Files skipped from review as they are similar to previous changes (1)
  • utils/tui/modelutils/file.go
Additional context used
golangci-lint
utils/tui/modelutils/option.go

[warning] 63-63: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


[warning] 5-5: exported: exported type ModeSelector should have comment or be unexported

(revive)


[warning] 14-14: exported: exported function NewModeSelector should have comment or be unexported

(revive)


[warning] 23-23: exported: exported method ModeSelector.Init should have comment or be unexported

(revive)


[warning] 27-27: exported: exported method ModeSelector.Update should have comment or be unexported

(revive)


[warning] 52-52: exported: exported method ModeSelector.View should have comment or be unexported

(revive)


90-90: unnecessary trailing newline

(whitespace)


65-65: unnecessary leading newline

(whitespace)

utils/tui/model.go

104-104: ineffectual assignment to counter

(ineffassign)


139-139: ineffectual assignment to counter

(ineffassign)

Additional comments not posted (9)
utils/tui/modelutils/text.go (4)

12-19: LGTM!

The LabelInput struct is well-defined.


21-28: LGTM!

The NewLabelInput function is well-defined.


30-32: LGTM!

The Init method is well-defined.


62-84: LGTM!

The View method is well-defined.

utils/tui/model.go (5)

13-29: LGTM!

The Model struct is well-defined.


36-39: LGTM!

The Init method is well-defined.


167-187: LGTM!

The View method is well-defined.


189-222: LGTM!

The applyChanges method is well-defined.


224-239: LGTM!

The AbsToRel function is well-defined.

Comment on lines 14 to 21
func NewModeSelector(choices []string, file string, speed string) ModeSelector {
return ModeSelector{
File: file,
Choices: choices,
Selected: "",
Speed: speed,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported NewModeSelector function.

The NewModeSelector function is exported and should have a comment for proper documentation.

+// NewModeSelector creates a new instance of ModeSelector with the given choices, file, and speed.
 func NewModeSelector(choices []string, file string, speed string) ModeSelector {
	return ModeSelector{
		File:     file,
		Choices:  choices,
		Selected: "",
		Speed:    speed,
	}
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func NewModeSelector(choices []string, file string, speed string) ModeSelector {
return ModeSelector{
File: file,
Choices: choices,
Selected: "",
Speed: speed,
}
}
// NewModeSelector creates a new instance of ModeSelector with the given choices, file, and speed.
func NewModeSelector(choices []string, file string, speed string) ModeSelector {
return ModeSelector{
File: file,
Choices: choices,
Selected: "",
Speed: speed,
}
}
Tools
golangci-lint

[warning] 14-14: exported: exported function NewModeSelector should have comment or be unexported

(revive)

Comment on lines 5 to 12
type ModeSelector struct {
File string
Choices []string
cursor int
Selected string
Done bool
Speed string
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported ModeSelector struct.

The ModeSelector struct is exported and should have a comment for proper documentation.

+// ModeSelector represents a mode selector in the TUI application.
 type ModeSelector struct {
	File     string
	Choices  []string
	cursor   int
	Selected string
	Done     bool
	Speed    string
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type ModeSelector struct {
File string
Choices []string
cursor int
Selected string
Done bool
Speed string
}
// ModeSelector represents a mode selector in the TUI application.
type ModeSelector struct {
File string
Choices []string
cursor int
Selected string
Done bool
Speed string
}
Tools
golangci-lint

[warning] 5-5: exported: exported type ModeSelector should have comment or be unexported

(revive)

Comment on lines 27 to 50
func (m ModeSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit

case "up":
if m.cursor > 0 {
m.cursor--
}

case "down":
if m.cursor < len(m.Choices)-1 {
m.cursor++
}

case "enter":
m.Selected = m.Choices[m.cursor]
m.Done = true
}
}
return m, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported Update method.

The Update method is exported and should have a comment for proper documentation.

+// Update processes incoming messages and updates the ModeSelector state accordingly.
 func (m ModeSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "q":
			return m, tea.Quit

		case "up":
			if m.cursor > 0 {
				m.cursor--
			}

		case "down":
			if m.cursor < len(m.Choices)-1 {
				m.cursor++
			}

		case "enter":
			m.Selected = m.Choices[m.cursor]
			m.Done = true
		}
	}
	return m, nil
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m ModeSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
}
case "down":
if m.cursor < len(m.Choices)-1 {
m.cursor++
}
case "enter":
m.Selected = m.Choices[m.cursor]
m.Done = true
}
}
return m, nil
}
// Update processes incoming messages and updates the ModeSelector state accordingly.
func (m ModeSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
}
case "down":
if m.cursor < len(m.Choices)-1 {
m.cursor++
}
case "enter":
m.Selected = m.Choices[m.cursor]
m.Done = true
}
}
return m, nil
}
Tools
golangci-lint

[warning] 27-27: exported: exported method ModeSelector.Update should have comment or be unexported

(revive)

Comment on lines 52 to 90
func (m ModeSelector) View() string {
if len(m.Choices) == 2 {
s := Paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
return s
} else {
s := ""
switch m.Speed {

case "Slow mode":
s += Paint("silver").Render("Select action for file: "+m.File) + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}

case "Fast mode":
s += Paint("silver").Render("Select action:") + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
}
return s + Paint("silver").Render("\n 'q' to quit 'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported View method and fix the indent-error-flow issue.

The View method is exported and should have a comment for proper documentation. Additionally, the indent-error-flow issue should be fixed by removing the unnecessary else block.

+// View renders the view for the ModeSelector based on its current state.
 func (m ModeSelector) View() string {
	if len(m.Choices) == 2 {
		s := Paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
		for i, choice := range m.Choices {
			cursor := " "
			if m.cursor == i {
				cursor = ">"
			}
			s += cursor + " " + choice + "\n"
		}
		return s
	}
	s := ""
	switch m.Speed {

	case "Slow mode":
		s += Paint("silver").Render("Select action for file: "+m.File) + "\n\n"
		for i, choice := range m.Choices {
			cursor := " "
			if m.cursor == i {
				cursor = ">"
			}
			s += cursor + " " + choice + "\n"
		}

	case "Fast mode":
		s += Paint("silver").Render("Select action:") + "\n\n"
		for i, choice := range m.Choices {
			cursor := " "
			if m.cursor == i {
				cursor = ">"
			}
			s += cursor + " " + choice + "\n"
		}
	}
	return s + Paint("silver").Render("\n 'q' to quit     'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m ModeSelector) View() string {
if len(m.Choices) == 2 {
s := Paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
return s
} else {
s := ""
switch m.Speed {
case "Slow mode":
s += Paint("silver").Render("Select action for file: "+m.File) + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
case "Fast mode":
s += Paint("silver").Render("Select action:") + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
}
return s + Paint("silver").Render("\n 'q' to quit 'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}
}
// View renders the view for the ModeSelector based on its current state.
func (m ModeSelector) View() string {
if len(m.Choices) == 2 {
s := Paint("silver").Render("Select 'Fast mode' if you want to toggle all your files by giving just indications about start label and end label.\nSelect 'Slow mode' if you want to specify what action to perform file by file.") + "\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
return s
}
s := ""
switch m.Speed {
case "Slow mode":
s += Paint("silver").Render("Select action for file: "+m.File) + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
case "Fast mode":
s += Paint("silver").Render("Select action:") + "\n\n"
for i, choice := range m.Choices {
cursor := " "
if m.cursor == i {
cursor = ">"
}
s += cursor + " " + choice + "\n"
}
}
return s + Paint("silver").Render("\n 'q' to quit 'enter' to modify selected files\n '↑' to go up\n '↓' to go down")
}
Tools
golangci-lint

[warning] 63-63: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


[warning] 52-52: exported: exported method ModeSelector.View should have comment or be unexported

(revive)


90-90: unnecessary trailing newline

(whitespace)


65-65: unnecessary leading newline

(whitespace)

Comment on lines 34 to 60
func (m LabelInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit

case "enter":
if err := m.validateInput(); err != nil {
m.Error = err
return m, nil
}
m.Done = true

case "backspace":
if len(m.Input) > 0 {
m.Input = m.Input[:len(m.Input)-1]
}

default:
m.Input += msg.String()
}
case tickMsg:
m.flash = !m.flash
}
return m, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper error handling in the Update method.

The method validateInput() is called within a conditional block when the "enter" key is pressed. If an error occurs, it's correctly assigned to m.Error, but there should be a way to inform the user about the error, perhaps by flashing the screen or showing an error message directly in the UI.

utils/tui/model.go Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 7be875d and 371c869.

Files selected for processing (3)
  • utils/tui/model.go (1 hunks)
  • utils/tui/modelutils/file.go (1 hunks)
  • utils/tui/modelutils/text.go (1 hunks)
Additional context used
golangci-lint
utils/tui/modelutils/text.go

[warning] 12-12: exported: exported type LabelInput should have comment or be unexported

(revive)


[warning] 21-21: exported: exported function NewLabelInput should have comment or be unexported

(revive)


[warning] 30-30: exported: exported method LabelInput.Init should have comment or be unexported

(revive)


[warning] 34-34: exported: exported method LabelInput.Update should have comment or be unexported

(revive)


[warning] 63-63: exported: exported method LabelInput.View should have comment or be unexported

(revive)


[warning] 87-87: exported: exported function StartTicker should have comment or be unexported

(revive)

utils/tui/modelutils/file.go

[warning] 11-11: exported: exported type FilesSelector should have comment or be unexported

(revive)


[warning] 24-24: exported: exported function InitialModel should have comment or be unexported

(revive)


[warning] 53-53: exported: exported method FilesSelector.Init should have comment or be unexported

(revive)

utils/tui/model.go

104-104: ineffectual assignment to counter

(ineffassign)


139-139: ineffectual assignment to counter

(ineffassign)

Additional comments not posted (6)
utils/tui/modelutils/file.go (1)

53-55: Add a comment for the exported method Init.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // Init initializes the FilesSelector struct.
  func (m FilesSelector) Init() tea.Cmd {

Likely invalid or redundant comment.

Tools
golangci-lint

[warning] 53-53: exported: exported method FilesSelector.Init should have comment or be unexported

(revive)

utils/tui/model.go (5)

189-222: Add a comment for the exported method applyChanges.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // applyChanges applies changes to selected files based on user inputs.
  func (m *Model) applyChanges() tea.Cmd {

Likely invalid or redundant comment.


36-39: Add a comment for the exported method Init.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // Init initializes the model.
  func (m Model) Init() tea.Cmd {

Likely invalid or redundant comment.


13-29: Add a comment for the exported struct Model.

According to Go conventions, exported structs should have a comment explaining their purpose.

+ // Model represents the main application model for the TUI.
  type Model struct {

Likely invalid or redundant comment.


31-34: Add a comment for the exported struct applyChangesMsg.

According to Go conventions, exported structs should have a comment explaining their purpose.

+ // applyChangesMsg represents a message indicating that changes have been applied.
  type applyChangesMsg struct {

Likely invalid or redundant comment.


167-187: Add a comment for the exported method View.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // View renders the view based on the current state.
  func (m Model) View() string {

Likely invalid or redundant comment.

Comment on lines 34 to 61
func (m LabelInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC:
return m, tea.Quit

case tea.KeyEnter:
if err := m.validateInput(); err != nil {
m.Error = err
return m, nil
}
m.Done = true

case tea.KeyBackspace:
if len(m.Input) > 0 {
m.Input = m.Input[:len(m.Input)-1]
}

case tea.KeyRunes:
m.Input += msg.String()
}

case tickMsg:
m.flash = !m.flash
}
return m, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported method Update and ensure proper error handling.

According to Go conventions, exported methods should have a comment explaining their purpose. Additionally, ensure proper error handling by informing the user about the error.

+ // Update updates the LabelInput struct based on user input.
  func (m LabelInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

To inform the user about the error, consider flashing the screen or showing an error message directly in the UI.

if err := m.validateInput(); err != nil {
  m.Error = err
  // Add code to inform the user about the error
  return m, nil
}
Tools
golangci-lint

[warning] 34-34: exported: exported method LabelInput.Update should have comment or be unexported

(revive)

Comment on lines 12 to 19
type LabelInput struct {
File string
Input string
Done bool
IsLabel bool // Added to distinguish between labels and line numbers
flash bool
Error error
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported struct LabelInput.

According to Go conventions, exported structs should have a comment explaining their purpose.

+ // LabelInput represents the input model for label or line number input.
  type LabelInput struct {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type LabelInput struct {
File string
Input string
Done bool
IsLabel bool // Added to distinguish between labels and line numbers
flash bool
Error error
}
// LabelInput represents the input model for label or line number input.
type LabelInput struct {
File string
Input string
Done bool
IsLabel bool // Added to distinguish between labels and line numbers
flash bool
Error error
}
Tools
golangci-lint

[warning] 12-12: exported: exported type LabelInput should have comment or be unexported

(revive)

Comment on lines 63 to 85
func (m LabelInput) View() string {
if m.Error != nil {
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
flash := ""
if m.flash {
flash = Paint("green").Render("▎")
}

s := Paint("silver").Render("Type below the section to modify. You can insert your start label\nand your end label using the syntax 'start';'end' or you can modify\n a single line by entering the line number or a range of lines using the syntax x-y") + "\n\n"
if m.File != "" {
s += Paint("green").Render(m.File+": ✏ "+m.Input) + flash + "\n"
} else {
s += Paint("green").Render("✏ "+m.Input) + flash + "\n"
}

if m.Error != nil {
s += Paint("red").Render("\nError: "+m.Error.Error()) + "\n"
}

s += Paint("silver").Render("\n 'ctrl +c' to quit 'enter' to select the lines/labels indicated\n '↑' to go up\n '↓' to go down")
return s
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported method View.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // View renders the view for the LabelInput struct.
  func (m LabelInput) View() string {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m LabelInput) View() string {
if m.Error != nil {
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
flash := ""
if m.flash {
flash = Paint("green").Render("▎")
}
s := Paint("silver").Render("Type below the section to modify. You can insert your start label\nand your end label using the syntax 'start';'end' or you can modify\n a single line by entering the line number or a range of lines using the syntax x-y") + "\n\n"
if m.File != "" {
s += Paint("green").Render(m.File+": ✏ "+m.Input) + flash + "\n"
} else {
s += Paint("green").Render("✏ "+m.Input) + flash + "\n"
}
if m.Error != nil {
s += Paint("red").Render("\nError: "+m.Error.Error()) + "\n"
}
s += Paint("silver").Render("\n 'ctrl +c' to quit 'enter' to select the lines/labels indicated\n '↑' to go up\n '↓' to go down")
return s
}
// View renders the view for the LabelInput struct.
func (m LabelInput) View() string {
if m.Error != nil {
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
flash := ""
if m.flash {
flash = Paint("green").Render("▎")
}
s := Paint("silver").Render("Type below the section to modify. You can insert your start label\nand your end label using the syntax 'start';'end' or you can modify\n a single line by entering the line number or a range of lines using the syntax x-y") + "\n\n"
if m.File != "" {
s += Paint("green").Render(m.File+": ✏ "+m.Input) + flash + "\n"
} else {
s += Paint("green").Render("✏ "+m.Input) + flash + "\n"
}
if m.Error != nil {
s += Paint("red").Render("\nError: "+m.Error.Error()) + "\n"
}
s += Paint("silver").Render("\n 'ctrl +c' to quit 'enter' to select the lines/labels indicated\n '↑' to go up\n '↓' to go down")
return s
}
Tools
golangci-lint

[warning] 63-63: exported: exported method LabelInput.View should have comment or be unexported

(revive)

Comment on lines 159 to 180
func Paint(color string) lipgloss.Style {
switch color {
case "lime":
lime := lipgloss.Color("#00FF00")
return lipgloss.NewStyle().Foreground(lime)
case "blue":
blue := lipgloss.Color("#0000FF")
return lipgloss.NewStyle().Foreground(blue)
case "green":
green := lipgloss.Color("#008000")
return lipgloss.NewStyle().Foreground(green)
case "red":
red := lipgloss.Color("#FF0000")
return lipgloss.NewStyle().Foreground(red)
case "silver":
silver := lipgloss.Color("#C0C0C0")
return lipgloss.NewStyle().Foreground(silver)
default:
white := lipgloss.Color("#FFFFFF")
return lipgloss.NewStyle().Foreground(white)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function Paint.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // Paint returns a lipgloss style based on the input color.
  func Paint(color string) lipgloss.Style {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func Paint(color string) lipgloss.Style {
switch color {
case "lime":
lime := lipgloss.Color("#00FF00")
return lipgloss.NewStyle().Foreground(lime)
case "blue":
blue := lipgloss.Color("#0000FF")
return lipgloss.NewStyle().Foreground(blue)
case "green":
green := lipgloss.Color("#008000")
return lipgloss.NewStyle().Foreground(green)
case "red":
red := lipgloss.Color("#FF0000")
return lipgloss.NewStyle().Foreground(red)
case "silver":
silver := lipgloss.Color("#C0C0C0")
return lipgloss.NewStyle().Foreground(silver)
default:
white := lipgloss.Color("#FFFFFF")
return lipgloss.NewStyle().Foreground(white)
}
}
// Paint returns a lipgloss style based on the input color.
func Paint(color string) lipgloss.Style {
switch color {
case "lime":
lime := lipgloss.Color("#00FF00")
return lipgloss.NewStyle().Foreground(lime)
case "blue":
blue := lipgloss.Color("#0000FF")
return lipgloss.NewStyle().Foreground(blue)
case "green":
green := lipgloss.Color("#008000")
return lipgloss.NewStyle().Foreground(green)
case "red":
red := lipgloss.Color("#FF0000")
return lipgloss.NewStyle().Foreground(red)
case "silver":
silver := lipgloss.Color("#C0C0C0")
return lipgloss.NewStyle().Foreground(silver)
default:
white := lipgloss.Color("#FFFFFF")
return lipgloss.NewStyle().Foreground(white)
}
}

Comment on lines 24 to 51
func InitialModel(currentDir string, windowHeight int) FilesSelector {
var filesAndDir []string
selectedFilesAndDir := make(map[int]bool)

entries, err := os.ReadDir(currentDir)
if err != nil {
return FilesSelector{Error: fmt.Errorf("error reading directory: %w", err)}
}

for _, entry := range entries {
entryPath, err := GetPathOfEntry(entry, currentDir)
if err != nil {
return FilesSelector{Error: fmt.Errorf("error getting path of entry: %w", err)}
}
filesAndDir = append(filesAndDir, entryPath)
}

for i := 0; i < len(filesAndDir); i++ {
selectedFilesAndDir[i] = false
}

return FilesSelector{
CurrentDir: currentDir,
FilesAndDir: filesAndDir,
SelectedFilesAndDir: selectedFilesAndDir,
WindowHeight: windowHeight,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function InitialModel.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // InitialModel initializes the FilesSelector struct.
  func InitialModel(currentDir string, windowHeight int) FilesSelector {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func InitialModel(currentDir string, windowHeight int) FilesSelector {
var filesAndDir []string
selectedFilesAndDir := make(map[int]bool)
entries, err := os.ReadDir(currentDir)
if err != nil {
return FilesSelector{Error: fmt.Errorf("error reading directory: %w", err)}
}
for _, entry := range entries {
entryPath, err := GetPathOfEntry(entry, currentDir)
if err != nil {
return FilesSelector{Error: fmt.Errorf("error getting path of entry: %w", err)}
}
filesAndDir = append(filesAndDir, entryPath)
}
for i := 0; i < len(filesAndDir); i++ {
selectedFilesAndDir[i] = false
}
return FilesSelector{
CurrentDir: currentDir,
FilesAndDir: filesAndDir,
SelectedFilesAndDir: selectedFilesAndDir,
WindowHeight: windowHeight,
}
}
// InitialModel initializes the FilesSelector struct.
func InitialModel(currentDir string, windowHeight int) FilesSelector {
var filesAndDir []string
selectedFilesAndDir := make(map[int]bool)
entries, err := os.ReadDir(currentDir)
if (err != nil) {
return FilesSelector{Error: fmt.Errorf("error reading directory: %w", err)}
}
for _, entry := range entries {
entryPath, err := GetPathOfEntry(entry, currentDir)
if err != nil {
return FilesSelector{Error: fmt.Errorf("error getting path of entry: %w", err)}
}
filesAndDir = append(filesAndDir, entryPath)
}
for i := 0; i < len(filesAndDir); i++ {
selectedFilesAndDir[i] = false
}
return FilesSelector{
CurrentDir: currentDir,
FilesAndDir: filesAndDir,
SelectedFilesAndDir: selectedFilesAndDir,
WindowHeight: windowHeight,
}
}
Tools
golangci-lint

[warning] 24-24: exported: exported function InitialModel should have comment or be unexported

(revive)

Comment on lines 118 to 157
func (m FilesSelector) View() string {
if m.Error != nil {
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}

s := Paint("silver").Render("\n Select the files you want to modify...") + "\n"
s += Paint("silver").Render("\n Selected files till now:") + "\n"
if m.NoFileSelected {
s += Paint("red").Render("\n No file selected. Please select at least one file or quit.") + "\n"
}
for i := 0; i < len(m.FilesPath); i++ {
s += fmt.Sprintf(" %s\n", Paint("green").Render(m.FilesPath[i]))
}
s += "\n"

for i := m.scrollOffset; i < m.scrollOffset+m.WindowHeight && i < len(m.FilesAndDir); i++ {
choice := m.FilesAndDir[i]
checkDir, err := IsDirectory(choice)
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
if checkDir {
choice = Paint("blue").Render("❒ " + choice)
} else if Contains(m.FilesPath, choice) {
choice = Paint("lime").Render("❒ " + choice)
} else {
choice = Paint("silver").Render("❒ " + choice)
}

cursor := " "
if m.cursor == i {
cursor = Paint("red").Render(" ➪")
}

s += fmt.Sprintf("%s %s\n", cursor, choice)
}
s += Paint("silver").Render("\n 'q' to quit 'esc' to move to parent directory\n '↑' to go up 'x' to modify selected files\n '↓' to go down 'enter' to select pointed file/move to pointed sub folder")
return s
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported method View.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // View renders the view of the FilesSelector struct for the TUI.
  func (m FilesSelector) View() string {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m FilesSelector) View() string {
if m.Error != nil {
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
s := Paint("silver").Render("\n Select the files you want to modify...") + "\n"
s += Paint("silver").Render("\n Selected files till now:") + "\n"
if m.NoFileSelected {
s += Paint("red").Render("\n No file selected. Please select at least one file or quit.") + "\n"
}
for i := 0; i < len(m.FilesPath); i++ {
s += fmt.Sprintf(" %s\n", Paint("green").Render(m.FilesPath[i]))
}
s += "\n"
for i := m.scrollOffset; i < m.scrollOffset+m.WindowHeight && i < len(m.FilesAndDir); i++ {
choice := m.FilesAndDir[i]
checkDir, err := IsDirectory(choice)
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
if checkDir {
choice = Paint("blue").Render("❒ " + choice)
} else if Contains(m.FilesPath, choice) {
choice = Paint("lime").Render("❒ " + choice)
} else {
choice = Paint("silver").Render("❒ " + choice)
}
cursor := " "
if m.cursor == i {
cursor = Paint("red").Render(" ➪")
}
s += fmt.Sprintf("%s %s\n", cursor, choice)
}
s += Paint("silver").Render("\n 'q' to quit 'esc' to move to parent directory\n '↑' to go up 'x' to modify selected files\n '↓' to go down 'enter' to select pointed file/move to pointed sub folder")
return s
}
// View renders the view of the FilesSelector struct for the TUI.
func (m FilesSelector) View() string {
if m.Error != nil {
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
s := Paint("silver").Render("\n Select the files you want to modify...") + "\n"
s += Paint("silver").Render("\n Selected files till now:") + "\n"
if m.NoFileSelected {
s += Paint("red").Render("\n No file selected. Please select at least one file or quit.") + "\n"
}
for i := 0; i < len(m.FilesPath); i++ {
s += fmt.Sprintf(" %s\n", Paint("green").Render(m.FilesPath[i]))
}
s += "\n"
for i := m.scrollOffset; i < m.scrollOffset+m.WindowHeight && i < len(m.FilesAndDir); i++ {
choice := m.FilesAndDir[i]
checkDir, err := IsDirectory(choice)
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return Paint("red").Render(fmt.Sprintf("An error occurred: %v", m.Error))
}
if checkDir {
choice = Paint("blue").Render("❒ " + choice)
} else if Contains(m.FilesPath, choice) {
choice = Paint("lime").Render("❒ " + choice)
} else {
choice = Paint("silver").Render("❒ " + choice)
}
cursor := " "
if m.cursor == i {
cursor = Paint("red").Render(" ➪")
}
s += fmt.Sprintf("%s %s\n", cursor, choice)
}
s += Paint("silver").Render("\n 'q' to quit 'esc' to move to parent directory\n '↑' to go up 'x' to modify selected files\n '↓' to go down 'enter' to select pointed file/move to pointed sub folder")
return s
}

Comment on lines 11 to 22
type FilesSelector struct {
CurrentDir string
FilesAndDir []string
SelectedFilesAndDir map[int]bool
FilesPath []string
cursor int
scrollOffset int
Done bool
WindowHeight int
Error error
NoFileSelected bool
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported struct FilesSelector.

According to Go conventions, exported structs should have a comment explaining their purpose.

+ // FilesSelector represents the main application model for the TUI.
  type FilesSelector struct {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type FilesSelector struct {
CurrentDir string
FilesAndDir []string
SelectedFilesAndDir map[int]bool
FilesPath []string
cursor int
scrollOffset int
Done bool
WindowHeight int
Error error
NoFileSelected bool
}
// FilesSelector represents the main application model for the TUI.
type FilesSelector struct {
CurrentDir string
FilesAndDir []string
SelectedFilesAndDir map[int]bool
FilesPath []string
cursor int
scrollOffset int
Done bool
WindowHeight int
Error error
NoFileSelected bool
}
Tools
golangci-lint

[warning] 11-11: exported: exported type FilesSelector should have comment or be unexported

(revive)

Comment on lines 57 to 115
func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.Error != nil {
return m, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
if m.cursor < m.scrollOffset {
m.scrollOffset--
}
}
case "down":
if m.cursor < len(m.FilesAndDir)-1 {
m.cursor++
if m.cursor >= m.scrollOffset+m.WindowHeight {
m.scrollOffset++
}
}
case "enter":
m.NoFileSelected = false
checkDir, err := IsDirectory(m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
if checkDir {
err := moveToNextDir(&m, m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
} else {
if Contains(m.FilesPath, m.FilesAndDir[m.cursor]) {
m.FilesPath = Remove(m.FilesPath, m.FilesAndDir[m.cursor])
} else {
m.FilesPath = append(m.FilesPath, m.FilesAndDir[m.cursor])
}
m.SelectedFilesAndDir[m.cursor] = !m.SelectedFilesAndDir[m.cursor]
}
case "esc":
err := moveToPreviousDir(&m)
if err != nil {
m.Error = fmt.Errorf("error moving back: %w", err)
return m, tea.Quit
}
case "x":
if len(m.FilesPath) == 0 {
m.NoFileSelected = true
} else {
m.Done = true
}
}
}
return m, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported method Update.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // Update updates the state of the FilesSelector struct based on user input.
  func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.Error != nil {
return m, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
if m.cursor < m.scrollOffset {
m.scrollOffset--
}
}
case "down":
if m.cursor < len(m.FilesAndDir)-1 {
m.cursor++
if m.cursor >= m.scrollOffset+m.WindowHeight {
m.scrollOffset++
}
}
case "enter":
m.NoFileSelected = false
checkDir, err := IsDirectory(m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
if checkDir {
err := moveToNextDir(&m, m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
} else {
if Contains(m.FilesPath, m.FilesAndDir[m.cursor]) {
m.FilesPath = Remove(m.FilesPath, m.FilesAndDir[m.cursor])
} else {
m.FilesPath = append(m.FilesPath, m.FilesAndDir[m.cursor])
}
m.SelectedFilesAndDir[m.cursor] = !m.SelectedFilesAndDir[m.cursor]
}
case "esc":
err := moveToPreviousDir(&m)
if err != nil {
m.Error = fmt.Errorf("error moving back: %w", err)
return m, tea.Quit
}
case "x":
if len(m.FilesPath) == 0 {
m.NoFileSelected = true
} else {
m.Done = true
}
}
}
return m, nil
// Update updates the state of the FilesSelector struct based on user input.
func (m FilesSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.Error != nil {
return m, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.cursor > 0 {
m.cursor--
if m.cursor < m.scrollOffset {
m.scrollOffset--
}
}
case "down":
if m.cursor < len(m.FilesAndDir)-1 {
m.cursor++
if m.cursor >= m.scrollOffset+m.WindowHeight {
m.scrollOffset++
}
}
case "enter":
m.NoFileSelected = false
checkDir, err := IsDirectory(m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
if checkDir {
err := moveToNextDir(&m, m.FilesAndDir[m.cursor])
if err != nil {
m.Error = fmt.Errorf("error checking directory: %w", err)
return m, tea.Quit
}
} else {
if Contains(m.FilesPath, m.FilesAndDir[m.cursor]) {
m.FilesPath = Remove(m.FilesPath, m.FilesAndDir[m.cursor])
} else {
m.FilesPath = append(m.FilesPath, m.FilesAndDir[m.cursor])
}
m.SelectedFilesAndDir[m.cursor] = !m.SelectedFilesAndDir[m.cursor]
}
case "esc":
err := moveToPreviousDir(&m)
if err != nil {
m.Error = fmt.Errorf("error moving back: %w", err)
return m, tea.Quit
}
case "x":
if len(m.FilesPath) == 0 {
m.NoFileSelected = true
} else {
m.Done = true
}
}
}
return m, nil

utils/tui/model.go Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 371c869 and f0e3fdf.

Files selected for processing (1)
  • utils/modfile/modfile.go (1 hunks)
Additional context used
golangci-lint
utils/modfile/modfile.go

60-60: Error return value of file.Close is not checked

(errcheck)


103-103: Error return value of tmpFile.Close is not checked

(errcheck)


108-108: Error return value of tmpFile.Close is not checked

(errcheck)


109-109: Error return value of os.Remove is not checked

(errcheck)


116-116: Error return value of tmpFile.Close is not checked

(errcheck)


117-117: Error return value of os.Remove is not checked

(errcheck)


123-123: Error return value of tmpFile.Close is not checked

(errcheck)


124-124: Error return value of os.Remove is not checked

(errcheck)


130-130: Error return value of os.Remove is not checked

(errcheck)


141-141: Error return value of os.Remove is not checked

(errcheck)


41-41: unnecessary trailing newline

(whitespace)


198-198: unnecessary leading newline

(whitespace)


230-230: unnecessary leading newline

(whitespace)


241-241: unnecessary trailing newline

(whitespace)

Additional comments not posted (6)
utils/modfile/modfile.go (6)

16-25: LGTM! Struct definition is well-defined.

The Config struct is well-defined and follows Go conventions.


147-152: LGTM! Function is straightforward and correct.

The shouldProcessLine function is straightforward and appears correct.


257-276: LGTM! Function is straightforward and correct.

The createBackup function is straightforward and appears correct.


278-283: LGTM! Function is straightforward and correct.

The restoreBackup function is straightforward and appears correct.


285-308: LGTM! Function is straightforward and correct.

The findLines function is straightforward and appears correct.


387-419: LGTM! Variable definition is well-defined.

The CommentChars variable is well-defined and follows Go conventions.

Comment on lines 44 to 144
func ChangeFile(conf Config) error {
var file *os.File
var err error
var isStdin bool

if conf.Filename == "" {
// Read from stdin
file = os.Stdin
isStdin = true
} else {
// Open the file
file, err = os.Open(conf.Filename)
if err != nil {
return err
}
defer file.Close()
}

char, err := selectCommentChars(conf.Filename, conf.Lang)
if err != nil {
return err
}
modFunc, err := setModFunc(conf.Action)
if err != nil {
return err
}
lines := [2]int{0, 0}
if conf.LineNum != "" {
lines, err = findLines(conf.LineNum)
if err != nil {
return err
}
}
if conf.DryRun {
err := printChanges(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
if err != nil {
return fmt.Errorf("failed to process the file: %s", err)
}
} else {
if isStdin {
err := printOutput(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
if err != nil {
return fmt.Errorf("failed to process the file: %s", err)
}
} else {
// Create a backup of the original file
backupFilename := conf.Filename + ".bak"
if err := createBackup(conf.Filename, backupFilename); err != nil {
return err
}

// Create a temporary file
tmpFilename := conf.Filename + ".tmp"
tmpFile, err := os.Create(tmpFilename)
if err != nil {
restoreBackup(conf.Filename, backupFilename)
return err
}
defer tmpFile.Close()

_, err = file.Seek(0, io.SeekStart)
if err != nil {
restoreBackup(conf.Filename, backupFilename)
tmpFile.Close()
os.Remove(tmpFilename)
return err
}

err = writeChanges(file, tmpFile, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
if err != nil {
restoreBackup(conf.Filename, backupFilename)
tmpFile.Close()
os.Remove(tmpFilename)
return err
}

if err := file.Close(); err != nil {
restoreBackup(conf.Filename, backupFilename)
tmpFile.Close()
os.Remove(tmpFilename)
return err
}

// Close the temporary file before renaming
if err := tmpFile.Close(); err != nil {
os.Remove(tmpFilename)
return err
}

// Rename temporary file to original file
if err := os.Rename(tmpFilename, conf.Filename); err != nil {
restoreBackup(conf.Filename, backupFilename)
return err
}

// Remove backup file after successful processing
os.Remove(backupFilename)
}
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring the ChangeFile function for better readability and maintainability.

The function is lengthy and could be broken down into smaller helper functions.

func ChangeFile(conf Config) error {
    file, isStdin, err := openFile(conf.Filename)
    if err != nil {
        return err
    }
    defer func() {
        if err := file.Close(); err != nil {
            log.Printf("failed to close file: %v", err)
        }
    }()

    char, err := selectCommentChars(conf.Filename, conf.Lang)
    if err != nil {
        return err
    }
    modFunc, err := setModFunc(conf.Action)
    if err != nil {
        return err
    }
    lines, err := getLines(conf.LineNum)
    if err != nil {
        return err
    }

    if conf.DryRun {
        return printChanges(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
    }

    if isStdin {
        return printOutput(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
    }

    return processFile(file, conf.Filename, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
}
Tools
golangci-lint

60-60: Error return value of file.Close is not checked

(errcheck)


103-103: Error return value of tmpFile.Close is not checked

(errcheck)


108-108: Error return value of tmpFile.Close is not checked

(errcheck)


109-109: Error return value of os.Remove is not checked

(errcheck)


116-116: Error return value of tmpFile.Close is not checked

(errcheck)


117-117: Error return value of os.Remove is not checked

(errcheck)


123-123: Error return value of tmpFile.Close is not checked

(errcheck)


124-124: Error return value of os.Remove is not checked

(errcheck)


130-130: Error return value of os.Remove is not checked

(errcheck)


141-141: Error return value of os.Remove is not checked

(errcheck)

Comment on lines 310 to 385
func selectCommentChars(filename, lang string) (string, error) {
if lang != "" {
lang = strings.ToLower(lang)
commentChars, ok := CommentChars[lang]
if !ok {
return "", fmt.Errorf("unsupported language: %s", lang)
}
return commentChars, nil
}

if filename != "" {
extension := filepath.Ext(filename)
switch extension {
case ".go":
return CommentChars["golang"], nil
case ".js":
return CommentChars["js"], nil
case ".sh", ".bash":
return CommentChars["bash"], nil
case ".cpp", ".cc", ".h", ".c":
return CommentChars["c"], nil
case ".java":
return CommentChars["java"], nil
case ".py":
return CommentChars["python"], nil
case ".rb":
return CommentChars["ruby"], nil
case ".pl":
return CommentChars["perl"], nil
case ".php":
return CommentChars["php"], nil
case ".swift":
return CommentChars["swift"], nil
case ".kt", ".kts":
return CommentChars["kotlin"], nil
case ".R":
return CommentChars["r"], nil
case ".hs":
return CommentChars["haskell"], nil
case ".sql":
return CommentChars["sql"], nil
case ".rs":
return CommentChars["rust"], nil
case ".scala":
return CommentChars["scala"], nil
case ".dart":
return CommentChars["dart"], nil
case ".mm":
return CommentChars["objective-c"], nil
case ".m":
return CommentChars["matlab"], nil
case ".lua":
return CommentChars["lua"], nil
case ".erl":
return CommentChars["erlang"], nil
case ".ex", ".exs":
return CommentChars["elixir"], nil
case ".ts":
return CommentChars["ts"], nil
case ".vhdl", ".vhd":
return CommentChars["vhdl"], nil
case ".v", ".sv":
return CommentChars["verilog"], nil
case ".slang":
return CommentChars["slangroom"], nil
case ".zen":
return CommentChars["zenroom"], nil
case ".html":
return CommentChars["html"], nil
default:
return "", fmt.Errorf("unsupported file extension: %s", extension)
}
}

return "", fmt.Errorf("language not specified and no filename provided")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring the selectCommentChars function for better readability and maintainability.

The function is lengthy and could be broken down into smaller helper functions.

func selectCommentChars(filename, lang string) (string, error) {
    if lang != "" {
        return getCommentCharsByLang(lang)
    }

    if filename != "" {
        return getCommentCharsByExtension(filepath.Ext(filename))
    }

    return "", fmt.Errorf("language not specified and no filename provided")
}

func getCommentCharsByLang(lang string) (string, error) {
    lang = strings.ToLower(lang)
    commentChars, ok := CommentChars[lang]
    if !ok {
        return "", fmt.Errorf("unsupported language: %s", lang)
    }
    return commentChars, nil
}

func getCommentCharsByExtension(extension string) (string, error) {
    switch extension {
    case ".go":
        return CommentChars["golang"], nil
    case ".js":
        return CommentChars["js"], nil
    // Add remaining cases here
    default:
        return "", fmt.Errorf("unsupported file extension: %s", extension)
    }
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 25

Outside diff range and nitpick comments (1)
utils/tui/modelutils/option.go (1)

Line range hint 266-281:
Add a comment for the exported function AbsToRel.

The AbsToRel function is exported and should have a comment for proper documentation.

+// AbsToRel converts an absolute path to a relative path.
 func AbsToRel(absPath string) (string, error) {
Tools
golangci-lint

93-93: unnecessary trailing newline

(whitespace)


68-68: unnecessary leading newline

(whitespace)

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between f0e3fdf and d6ddccf.

Files selected for processing (5)
  • cmd/root.go (1 hunks)
  • utils/modfile/modfile.go (1 hunks)
  • utils/tui/model.go (1 hunks)
  • utils/tui/modelutils/option.go (1 hunks)
  • utils/tui/modelutils/text.go (1 hunks)
Additional context used
golangci-lint
utils/tui/modelutils/option.go

93-93: unnecessary trailing newline

(whitespace)


68-68: unnecessary leading newline

(whitespace)

utils/tui/modelutils/text.go

[warning] 12-12: exported: exported type LabelInput should have comment or be unexported

(revive)


[warning] 22-22: exported: exported function NewLabelInput should have comment or be unexported

(revive)


[warning] 31-31: exported: exported method LabelInput.Init should have comment or be unexported

(revive)


[warning] 35-35: exported: exported method LabelInput.Update should have comment or be unexported

(revive)

utils/tui/model.go

109-109: unnecessary trailing newline

(whitespace)


121-121: unnecessary trailing newline

(whitespace)

utils/modfile/modfile.go

60-60: Error return value of file.Close is not checked

(errcheck)


103-103: Error return value of tmpFile.Close is not checked

(errcheck)


108-108: Error return value of tmpFile.Close is not checked

(errcheck)


109-109: Error return value of os.Remove is not checked

(errcheck)


116-116: Error return value of tmpFile.Close is not checked

(errcheck)


117-117: Error return value of os.Remove is not checked

(errcheck)


123-123: Error return value of tmpFile.Close is not checked

(errcheck)


124-124: Error return value of os.Remove is not checked

(errcheck)


130-130: Error return value of os.Remove is not checked

(errcheck)


141-141: Error return value of os.Remove is not checked

(errcheck)


41-41: unnecessary trailing newline

(whitespace)


198-198: unnecessary leading newline

(whitespace)


230-230: unnecessary leading newline

(whitespace)


241-241: unnecessary trailing newline

(whitespace)

Additional comments not posted (4)
utils/tui/modelutils/option.go (1)

15-22: Add a comment for the exported NewModeSelector function.

The NewModeSelector function is exported and should have a comment for proper documentation.

+// NewModeSelector creates a new instance of ModeSelector with the given choices, file, and speed.
 func NewModeSelector(choices []string, file string, speed string) ModeSelector {

Likely invalid or redundant comment.

utils/modfile/modfile.go (3)

147-152: LGTM!

The function is simple and correct.


257-276: LGTM!

The function is simple and correct.


278-283: LGTM!

The function is simple and correct.

Comment on lines 44 to 145
var file *os.File
var err error
var isStdin bool

if conf.Filename == "" {
// Read from stdin
file = os.Stdin
isStdin = true
} else {
// Open the file
file, err = os.Open(conf.Filename)
if err != nil {
return err
}
defer file.Close()
}

char, err := selectCommentChars(conf.Filename, conf.Lang)
if err != nil {
return err
}
modFunc, err := setModFunc(conf.Action)
if err != nil {
return err
}
lines := [2]int{0, 0}
if conf.LineNum != "" {
lines, err = findLines(conf.LineNum)
if err != nil {
return err
}
}
if conf.DryRun {
err := printChanges(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
if err != nil {
return fmt.Errorf("failed to process the file: %s", err)
}
} else {
if isStdin {
err := printOutput(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
if err != nil {
return fmt.Errorf("failed to process the file: %s", err)
}
} else {
// Create a backup of the original file
backupFilename := conf.Filename + ".bak"
if err := createBackup(conf.Filename, backupFilename); err != nil {
return err
}

// Create a temporary file
tmpFilename := conf.Filename + ".tmp"
tmpFile, err := os.Create(tmpFilename)
if err != nil {
restoreBackup(conf.Filename, backupFilename)
return err
}
defer tmpFile.Close()

_, err = file.Seek(0, io.SeekStart)
if err != nil {
restoreBackup(conf.Filename, backupFilename)
tmpFile.Close()
os.Remove(tmpFilename)
return err
}

err = writeChanges(file, tmpFile, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
if err != nil {
restoreBackup(conf.Filename, backupFilename)
tmpFile.Close()
os.Remove(tmpFilename)
return err
}

if err := file.Close(); err != nil {
restoreBackup(conf.Filename, backupFilename)
tmpFile.Close()
os.Remove(tmpFilename)
return err
}

// Close the temporary file before renaming
if err := tmpFile.Close(); err != nil {
os.Remove(tmpFilename)
return err
}

// Rename temporary file to original file
if err := os.Rename(tmpFilename, conf.Filename); err != nil {
restoreBackup(conf.Filename, backupFilename)
return err
}

// Remove backup file after successful processing
os.Remove(backupFilename)
}
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor the ChangeFile function for better readability and maintainability.

The function is lengthy and could be broken down into smaller helper functions.

func ChangeFile(conf Config) error {
    file, isStdin, err := openFile(conf.Filename)
    if err != nil {
        return err
    }
    defer func() {
        if err := file.Close(); err != nil {
            log.Printf("failed to close file: %v", err)
        }
    }()

    char, err := selectCommentChars(conf.Filename, conf.Lang)
    if err != nil {
        return err
    }
    modFunc, err := setModFunc(conf.Action)
    if err != nil {
        return err
    }
    lines, err := getLines(conf.LineNum)
    if err != nil {
        return err
    }

    if conf.DryRun {
        return printChanges(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
    }

    if isStdin {
        return printOutput(file, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
    }

    return processFile(file, conf.Filename, lines, conf.StartLabel, conf.EndLabel, char, modFunc)
}
Tools
golangci-lint

60-60: Error return value of file.Close is not checked

(errcheck)


103-103: Error return value of tmpFile.Close is not checked

(errcheck)


108-108: Error return value of tmpFile.Close is not checked

(errcheck)


109-109: Error return value of os.Remove is not checked

(errcheck)


116-116: Error return value of tmpFile.Close is not checked

(errcheck)


117-117: Error return value of os.Remove is not checked

(errcheck)


123-123: Error return value of tmpFile.Close is not checked

(errcheck)


124-124: Error return value of os.Remove is not checked

(errcheck)


130-130: Error return value of os.Remove is not checked

(errcheck)


141-141: Error return value of os.Remove is not checked

(errcheck)

Comment on lines 310 to 385
func selectCommentChars(filename, lang string) (string, error) {
if lang != "" {
lang = strings.ToLower(lang)
commentChars, ok := CommentChars[lang]
if !ok {
return "", fmt.Errorf("unsupported language: %s", lang)
}
return commentChars, nil
}

if filename != "" {
extension := filepath.Ext(filename)
switch extension {
case ".go":
return CommentChars["golang"], nil
case ".js":
return CommentChars["js"], nil
case ".sh", ".bash":
return CommentChars["bash"], nil
case ".cpp", ".cc", ".h", ".c", ".cs":
return CommentChars["c"], nil
case ".java":
return CommentChars["java"], nil
case ".py":
return CommentChars["python"], nil
case ".rb":
return CommentChars["ruby"], nil
case ".pl":
return CommentChars["perl"], nil
case ".php":
return CommentChars["php"], nil
case ".swift":
return CommentChars["swift"], nil
case ".kt", ".kts":
return CommentChars["kotlin"], nil
case ".R":
return CommentChars["r"], nil
case ".hs":
return CommentChars["haskell"], nil
case ".sql":
return CommentChars["sql"], nil
case ".rs":
return CommentChars["rust"], nil
case ".scala":
return CommentChars["scala"], nil
case ".dart":
return CommentChars["dart"], nil
case ".mm":
return CommentChars["objective-c"], nil
case ".m":
return CommentChars["matlab"], nil
case ".lua":
return CommentChars["lua"], nil
case ".erl":
return CommentChars["erlang"], nil
case ".ex", ".exs":
return CommentChars["elixir"], nil
case ".ts":
return CommentChars["ts"], nil
case ".vhdl", ".vhd":
return CommentChars["vhdl"], nil
case ".v", ".sv":
return CommentChars["verilog"], nil
case ".slang":
return CommentChars["slangroom"], nil
case ".zen":
return CommentChars["zenroom"], nil
case ".html":
return CommentChars["html"], nil
default:
return "", fmt.Errorf("unsupported file extension: %s", extension)
}
}

return "", fmt.Errorf("language not specified and no filename provided")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring the selectCommentChars function for better readability and maintainability.

The function is lengthy and could be broken down into smaller helper functions.

func selectCommentChars(filename, lang string) (string, error) {
    if lang != "" {
        return getCommentCharsByLang(lang)
    }

    if filename != "" {
        return getCommentCharsByExtension(filepath.Ext(filename))
    }

    return "", fmt.Errorf("language not specified and no filename provided")
}

func getCommentCharsByLang(lang string) (string, error) {
    lang = strings.ToLower(lang)
    commentChars, ok := CommentChars[lang]
    if !ok {
        return "", fmt.Errorf("unsupported language: %s", lang)
    }
    return commentChars, nil
}

func getCommentCharsByExtension(extension string) (string, error) {
    switch extension {
    case ".go":
        return CommentChars["golang"], nil
    case ".js":
        return CommentChars["js"], nil
    // Add remaining cases here
    default:
        return "", fmt.Errorf("unsupported file extension: %s", extension)
    }
}

Comment on lines 225 to 255
func printOutput(input *os.File, lineNum [2]int, startLabel, endLabel, commentChars string, modFunc func(string, string) string) error {
scanner := bufio.NewScanner(input)
currentLine := 1
inSection := false

for scanner.Scan() {

lineContent := scanner.Text()

if strings.Contains(lineContent, endLabel) {
inSection = false
}

if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
lineContent = modFunc(lineContent, commentChars)

}

if strings.Contains(lineContent, startLabel) {
inSection = true
}
fmt.Println(lineContent)
currentLine++
}

if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
return errors.New("line number is out of range")
}

return scanner.Err()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify the logic for processing lines in printOutput.

The logic for processing lines can be simplified by combining conditions. Remove the unnecessary leading and trailing newlines.

-  scanner := bufio.NewScanner(input)
-  currentLine := 1
-  inSection := false
-  
-  for scanner.Scan() {
-    lineContent := scanner.Text()
-    if strings.Contains(lineContent, endLabel) {
-      inSection = false
-    }
-  
-    if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
-      lineContent = modFunc(lineContent, commentChars)
-    }
-  
-    if strings.Contains(lineContent, startLabel) {
-      inSection = true
-    }
-    fmt.Println(lineContent)
-    currentLine++
-  }
-  
-  if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
-    return errors.New("line number is out of range")
-  }
-  
-  return scanner.Err()
+  scanner := bufio.NewScanner(input)
+  currentLine := 1
+  inSection := false
+
+  for scanner.Scan() {
+    lineContent := scanner.Text()
+    if strings.Contains(lineContent, endLabel) {
+      inSection = false
+    }
+
+    if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
+      lineContent = modFunc(lineContent, commentChars)
+    }
+
+    if strings.Contains(lineContent, startLabel) {
+      inSection = true
+    }
+    fmt.Println(lineContent)
+    currentLine++
+  }
+
+  if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
+    return errors.New("line number is out of range")
+  }
+
+  return scanner.Err()

Committable suggestion was skipped due to low confidence.

Tools
golangci-lint

230-230: unnecessary leading newline

(whitespace)


241-241: unnecessary trailing newline

(whitespace)

Comment on lines 285 to 308
func findLines(lineStr string) ([2]int, error) {
if strings.Contains(lineStr, "-") {
parts := strings.Split(lineStr, "-")
if len(parts) != 2 {
return [2]int{0, 0}, fmt.Errorf("invalid range format. Use 'start-end'")
}
startLine, err := strconv.Atoi(parts[0])
if err != nil || startLine <= 0 {
return [2]int{0, 0}, fmt.Errorf("invalid start line number")
}
endLine, err := strconv.Atoi(parts[1])
if err != nil || endLine < startLine {
return [2]int{0, 0}, fmt.Errorf("invalid end line number")
}
return [2]int{startLine, endLine}, nil
} else {
startLine, err := strconv.Atoi(lineStr)
if err != nil || startLine <= 0 {
return [2]int{0, 0}, fmt.Errorf("please provide a valid positive integer for the line number or a range")
}
endLine := startLine
return [2]int{startLine, endLine}, nil
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify the findLines function and improve error messages.

The function can be simplified and the error messages can be more specific.

func findLines(lineStr string) ([2]int, error) {
    parts := strings.Split(lineStr, "-")
    if len(parts) == 1 {
        startLine, err := strconv.Atoi(parts[0])
        if err != nil || startLine <= 0 {
            return [2]int{0, 0}, fmt.Errorf("please provide a valid positive integer for the line number")
        }
        return [2]int{startLine, startLine}, nil
    }
    if len(parts) == 2 {
        startLine, err := strconv.Atoi(parts[0])
        if err != nil || startLine <= 0 {
            return [2]int{0, 0}, fmt.Errorf("invalid start line number")
        }
        endLine, err := strconv.Atoi(parts[1])
        if err != nil || endLine < startLine {
            return [2]int{0, 0}, fmt.Errorf("invalid end line number")
        }
        return [2]int{startLine, endLine}, nil
    }
    return [2]int{0, 0}, fmt.Errorf("invalid range format. Use 'start-end'")
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func findLines(lineStr string) ([2]int, error) {
if strings.Contains(lineStr, "-") {
parts := strings.Split(lineStr, "-")
if len(parts) != 2 {
return [2]int{0, 0}, fmt.Errorf("invalid range format. Use 'start-end'")
}
startLine, err := strconv.Atoi(parts[0])
if err != nil || startLine <= 0 {
return [2]int{0, 0}, fmt.Errorf("invalid start line number")
}
endLine, err := strconv.Atoi(parts[1])
if err != nil || endLine < startLine {
return [2]int{0, 0}, fmt.Errorf("invalid end line number")
}
return [2]int{startLine, endLine}, nil
} else {
startLine, err := strconv.Atoi(lineStr)
if err != nil || startLine <= 0 {
return [2]int{0, 0}, fmt.Errorf("please provide a valid positive integer for the line number or a range")
}
endLine := startLine
return [2]int{startLine, endLine}, nil
}
}
func findLines(lineStr string) ([2]int, error) {
parts := strings.Split(lineStr, "-")
if len(parts) == 1 {
startLine, err := strconv.Atoi(parts[0])
if err != nil || startLine <= 0 {
return [2]int{0, 0}, fmt.Errorf("please provide a valid positive integer for the line number")
}
return [2]int{startLine, startLine}, nil
}
if len(parts) == 2 {
startLine, err := strconv.Atoi(parts[0])
if err != nil || startLine <= 0 {
return [2]int{0, 0}, fmt.Errorf("invalid start line number")
}
endLine, err := strconv.Atoi(parts[1])
if err != nil || endLine < startLine {
return [2]int{0, 0}, fmt.Errorf("invalid end line number")
}
return [2]int{startLine, endLine}, nil
}
return [2]int{0, 0}, fmt.Errorf("invalid range format. Use 'start-end'")
}

Comment on lines 193 to 223
func printChanges(inputFile *os.File, lineNum [2]int, startLabel, endLabel, commentChars string, modFunc func(string, string) string) error {
scanner := bufio.NewScanner(inputFile)
currentLine := 1
inSection := false

for scanner.Scan() {

lineContent := scanner.Text()

if strings.Contains(lineContent, endLabel) {
inSection = false
}

if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
modified := modFunc(lineContent, commentChars)
fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
}

if strings.Contains(lineContent, startLabel) {
inSection = true
}

currentLine++
}

if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
return errors.New("line number is out of range")
}

return scanner.Err()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify the logic for processing lines in printChanges.

The logic for processing lines can be simplified by combining conditions. Remove the unnecessary leading newline.

-  scanner := bufio.NewScanner(inputFile)
-  currentLine := 1
-  inSection := false
-  
-  for scanner.Scan() {
-    lineContent := scanner.Text()
-    if strings.Contains(lineContent, endLabel) {
-      inSection = false
-    }
-  
-    if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
-      modified := modFunc(lineContent, commentChars)
-      fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
-    }
-  
-    if strings.Contains(lineContent, startLabel) {
-      inSection = true
-    }
-  
-    currentLine++
-  }
-  
-  if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
-    return errors.New("line number is out of range")
-  }
-  
-  return scanner.Err()
+  scanner := bufio.NewScanner(inputFile)
+  currentLine := 1
+  inSection := false
+
+  for scanner.Scan() {
+    lineContent := scanner.Text()
+    if strings.Contains(lineContent, endLabel) {
+      inSection = false
+    }
+
+    if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
+      modified := modFunc(lineContent, commentChars)
+      fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
+    }
+
+    if strings.Contains(lineContent, startLabel) {
+      inSection = true
+    }
+
+    currentLine++
+  }
+
+  if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
+    return errors.New("line number is out of range")
+  }
+
+  return scanner.Err()

Committable suggestion was skipped due to low confidence.

Tools
golangci-lint

198-198: unnecessary leading newline

(whitespace)

cmd/root.go Outdated
Comment on lines 189 to 203
func customUsageFunc(cmd *cobra.Command) error {
fmt.Println(cmd.Short)
fmt.Println("Usage:")
fmt.Printf(" %s\n", cmd.UseLine())
fmt.Println()
fmt.Println("Flags:")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "action" {
fmt.Printf(" -%s, --%s: %s (default: %s)\n", flag.Shorthand, flag.Name, flag.Usage, flag.DefValue)
} else {
fmt.Printf(" -%s, --%s: %s\n", flag.Shorthand, flag.Name, flag.Usage)
}
})
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the customUsageFunc function.

The customUsageFunc function provides custom usage information.

+// customUsageFunc provides custom usage information.
 func customUsageFunc(cmd *cobra.Command) error {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func customUsageFunc(cmd *cobra.Command) error {
fmt.Println(cmd.Short)
fmt.Println("Usage:")
fmt.Printf(" %s\n", cmd.UseLine())
fmt.Println()
fmt.Println("Flags:")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "action" {
fmt.Printf(" -%s, --%s: %s (default: %s)\n", flag.Shorthand, flag.Name, flag.Usage, flag.DefValue)
} else {
fmt.Printf(" -%s, --%s: %s\n", flag.Shorthand, flag.Name, flag.Usage)
}
})
return nil
}
// customUsageFunc provides custom usage information.
func customUsageFunc(cmd *cobra.Command) error {
fmt.Println(cmd.Short)
fmt.Println("Usage:")
fmt.Printf(" %s\n", cmd.UseLine())
fmt.Println()
fmt.Println("Flags:")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "action" {
fmt.Printf(" -%s, --%s: %s (default: %s)\n", flag.Shorthand, flag.Name, flag.Usage, flag.DefValue)
} else {
fmt.Printf(" -%s, --%s: %s\n", flag.Shorthand, flag.Name, flag.Usage)
}
})
return nil
}

cmd/root.go Outdated
Comment on lines 82 to 90
func noFlagsGiven(cmd *cobra.Command) bool {
hasFlags := false
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Changed {
hasFlags = true
}
})
return !hasFlags
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the noFlagsGiven function.

The noFlagsGiven function checks if any flags were provided.

+// noFlagsGiven checks if any flags were provided.
 func noFlagsGiven(cmd *cobra.Command) bool {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func noFlagsGiven(cmd *cobra.Command) bool {
hasFlags := false
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Changed {
hasFlags = true
}
})
return !hasFlags
}
// noFlagsGiven checks if any flags were provided.
func noFlagsGiven(cmd *cobra.Command) bool {
hasFlags := false
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Changed {
hasFlags = true
}
})
return !hasFlags
}

cmd/root.go Outdated
Comment on lines 159 to 187
func customHelpFunc(cmd *cobra.Command, args []string) {
fmt.Println("Tgcom CLI Application")
fmt.Println()
fmt.Println(cmd.Long)
fmt.Println()
fmt.Println("Usage:")
fmt.Println(" tgcom [flags]")
fmt.Println()
fmt.Println("Flags:")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "action" {
fmt.Printf(" -%s, --%s: %s (default: %s)\n", flag.Shorthand, flag.Name, flag.Usage, flag.DefValue)
} else {
fmt.Printf(" -%s, --%s: %s\n", flag.Shorthand, flag.Name, flag.Usage)
}
})
fmt.Println()
fmt.Println("Supported Languages:")
for lang := range modfile.CommentChars {
fmt.Println(lang)
}
fmt.Println()
fmt.Println("Examples:")
fmt.Println(" # Toggle comments on lines 1-5 in example.go")
fmt.Println(" tgcom -f example.go -l 1-5 -a toggle")
fmt.Println()
fmt.Println(" # Dry run: show the changes without modifying the file")
fmt.Println(" tgcom -f example.go -s START -e END -a toggle -d")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the customHelpFunc function.

The customHelpFunc function provides custom help information.

+// customHelpFunc provides custom help information.
 func customHelpFunc(cmd *cobra.Command, args []string) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func customHelpFunc(cmd *cobra.Command, args []string) {
fmt.Println("Tgcom CLI Application")
fmt.Println()
fmt.Println(cmd.Long)
fmt.Println()
fmt.Println("Usage:")
fmt.Println(" tgcom [flags]")
fmt.Println()
fmt.Println("Flags:")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "action" {
fmt.Printf(" -%s, --%s: %s (default: %s)\n", flag.Shorthand, flag.Name, flag.Usage, flag.DefValue)
} else {
fmt.Printf(" -%s, --%s: %s\n", flag.Shorthand, flag.Name, flag.Usage)
}
})
fmt.Println()
fmt.Println("Supported Languages:")
for lang := range modfile.CommentChars {
fmt.Println(lang)
}
fmt.Println()
fmt.Println("Examples:")
fmt.Println(" # Toggle comments on lines 1-5 in example.go")
fmt.Println(" tgcom -f example.go -l 1-5 -a toggle")
fmt.Println()
fmt.Println(" # Dry run: show the changes without modifying the file")
fmt.Println(" tgcom -f example.go -s START -e END -a toggle -d")
}
// customHelpFunc provides custom help information.
func customHelpFunc(cmd *cobra.Command, args []string) {
fmt.Println("Tgcom CLI Application")
fmt.Println()
fmt.Println(cmd.Long)
fmt.Println()
fmt.Println("Usage:")
fmt.Println(" tgcom [flags]")
fmt.Println()
fmt.Println("Flags:")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "action" {
fmt.Printf(" -%s, --%s: %s (default: %s)\n", flag.Shorthand, flag.Name, flag.Usage, flag.DefValue)
} else {
fmt.Printf(" -%s, --%s: %s\n", flag.Shorthand, flag.Name, flag.Usage)
}
})
fmt.Println()
fmt.Println("Supported Languages:")
for lang := range modfile.CommentChars {
fmt.Println(lang)
}
fmt.Println()
fmt.Println("Examples:")
fmt.Println(" # Toggle comments on lines 1-5 in example.go")
fmt.Println(" tgcom -f example.go -l 1-5 -a toggle")
fmt.Println()
fmt.Println(" # Dry run: show the changes without modifying the file")
fmt.Println(" tgcom -f example.go -s START -e END -a toggle -d")
}

cmd/root.go Outdated
Comment on lines 53 to 80
func init() {
rootCmd.SetHelpFunc(customHelpFunc)
rootCmd.SetUsageFunc(customUsageFunc)

rootCmd.PersistentFlags().StringVarP(&FileToRead, "file", "f", "", "pass argument to the flag and will modify the file content")
rootCmd.PersistentFlags().StringVarP(&inputFlag.LineNum, "line", "l", "", "pass argument to line flag and will modify the line in the specified range")
rootCmd.PersistentFlags().BoolVarP(&inputFlag.DryRun, "dry-run", "d", false, "pass argument to dry-run flag and will print the result")
rootCmd.PersistentFlags().StringVarP(&inputFlag.Action, "action", "a", "toggle", "pass argument to action to comment/uncomment/toggle some lines")
rootCmd.PersistentFlags().StringVarP(&inputFlag.StartLabel, "start-label", "s", "", "pass argument to start-label to modify lines after start-label")
rootCmd.PersistentFlags().StringVarP(&inputFlag.EndLabel, "end-label", "e", "", "pass argument to end-label to modify lines up to end-label")
rootCmd.PersistentFlags().StringVarP(&inputFlag.Lang, "language", "L", "", "pass argument to language to specify the language of the input code")
rootCmd.PersistentFlags().StringVarP(&remotePath, "remote", "w", "", "pass remote user, host, and directory in the format user@host:/path/to/directory")
rootCmd.PersistentFlags().BoolVarP(&Tui, "tui", "t", false, "run the terminal user interface")
// Mark flags based on command name
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if cmd.Name() != "server" {
cmd.MarkFlagsRequiredTogether("start-label", "end-label")
cmd.MarkFlagsMutuallyExclusive("line", "start-label")
cmd.MarkFlagsMutuallyExclusive("line", "end-label")
cmd.MarkFlagsOneRequired("file", "language", "remote", "tui")
cmd.MarkFlagsMutuallyExclusive("file", "language")
}
return nil
}

// Register server command
rootCmd.AddCommand(serverCmd)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the init function.

The init function sets help and usage functions, registers flags, and adds the server command.

+// init sets help and usage functions, registers flags, and adds the server command.
 func init() {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func init() {
rootCmd.SetHelpFunc(customHelpFunc)
rootCmd.SetUsageFunc(customUsageFunc)
rootCmd.PersistentFlags().StringVarP(&FileToRead, "file", "f", "", "pass argument to the flag and will modify the file content")
rootCmd.PersistentFlags().StringVarP(&inputFlag.LineNum, "line", "l", "", "pass argument to line flag and will modify the line in the specified range")
rootCmd.PersistentFlags().BoolVarP(&inputFlag.DryRun, "dry-run", "d", false, "pass argument to dry-run flag and will print the result")
rootCmd.PersistentFlags().StringVarP(&inputFlag.Action, "action", "a", "toggle", "pass argument to action to comment/uncomment/toggle some lines")
rootCmd.PersistentFlags().StringVarP(&inputFlag.StartLabel, "start-label", "s", "", "pass argument to start-label to modify lines after start-label")
rootCmd.PersistentFlags().StringVarP(&inputFlag.EndLabel, "end-label", "e", "", "pass argument to end-label to modify lines up to end-label")
rootCmd.PersistentFlags().StringVarP(&inputFlag.Lang, "language", "L", "", "pass argument to language to specify the language of the input code")
rootCmd.PersistentFlags().StringVarP(&remotePath, "remote", "w", "", "pass remote user, host, and directory in the format user@host:/path/to/directory")
rootCmd.PersistentFlags().BoolVarP(&Tui, "tui", "t", false, "run the terminal user interface")
// Mark flags based on command name
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if cmd.Name() != "server" {
cmd.MarkFlagsRequiredTogether("start-label", "end-label")
cmd.MarkFlagsMutuallyExclusive("line", "start-label")
cmd.MarkFlagsMutuallyExclusive("line", "end-label")
cmd.MarkFlagsOneRequired("file", "language", "remote", "tui")
cmd.MarkFlagsMutuallyExclusive("file", "language")
}
return nil
}
// Register server command
rootCmd.AddCommand(serverCmd)
}
// init sets help and usage functions, registers flags, and adds the server command.
func init() {
rootCmd.SetHelpFunc(customHelpFunc)
rootCmd.SetUsageFunc(customUsageFunc)
rootCmd.PersistentFlags().StringVarP(&FileToRead, "file", "f", "", "pass argument to the flag and will modify the file content")
rootCmd.PersistentFlags().StringVarP(&inputFlag.LineNum, "line", "l", "", "pass argument to line flag and will modify the line in the specified range")
rootCmd.PersistentFlags().BoolVarP(&inputFlag.DryRun, "dry-run", "d", false, "pass argument to dry-run flag and will print the result")
rootCmd.PersistentFlags().StringVarP(&inputFlag.Action, "action", "a", "toggle", "pass argument to action to comment/uncomment/toggle some lines")
rootCmd.PersistentFlags().StringVarP(&inputFlag.StartLabel, "start-label", "s", "", "pass argument to start-label to modify lines after start-label")
rootCmd.PersistentFlags().StringVarP(&inputFlag.EndLabel, "end-label", "e", "", "pass argument to end-label to modify lines up to end-label")
rootCmd.PersistentFlags().StringVarP(&inputFlag.Lang, "language", "L", "", "pass argument to language to specify the language of the input code")
rootCmd.PersistentFlags().StringVarP(&remotePath, "remote", "w", "", "pass remote user, host, and directory in the format user@host:/path/to/directory")
rootCmd.PersistentFlags().BoolVarP(&Tui, "tui", "t", false, "run the terminal user interface")
// Mark flags based on command name
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if cmd.Name() != "server" {
cmd.MarkFlagsRequiredTogether("start-label", "end-label")
cmd.MarkFlagsMutuallyExclusive("line", "start-label")
cmd.MarkFlagsMutuallyExclusive("line", "end-label")
cmd.MarkFlagsOneRequired("file", "language", "remote", "tui")
cmd.MarkFlagsMutuallyExclusive("file", "language")
}
return nil
}
// Register server command
rootCmd.AddCommand(serverCmd)
}

cmd/root.go Outdated
Comment on lines 92 to 157
func ReadFlags(cmd *cobra.Command) {
if Tui {
currentDir, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting current working directory: %v\n", err)
os.Exit(1)
}

// Initialize your model with the current directory
model := tui.Model{
State: "FileSelection",
FilesSelector: modelutils.InitialModel(currentDir, 20),
}
clearScreen()
// Bubble Tea program
p := tea.NewProgram(model)

// Start the program
if _, err := p.Run(); err != nil {
os.Exit(1)
}
clearScreen()
} else {

if strings.Contains(FileToRead, ",") {
if cmd.Flags().Changed("line") {
fmt.Println("Warning: when passing multiple files to flag -f, don't use -l flag")
}
if cmd.Flags().Changed("start-label") && cmd.Flags().Changed("end-label") {
fileInfo := strings.Split(FileToRead, ",")
for i := 0; i < len(fileInfo); i++ {
inputFlag.Filename = fileInfo[i]
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
}
} else {
fileInfo := strings.Split(FileToRead, ",")
for i := 0; i < len(fileInfo); i++ {
if strings.Contains(fileInfo[i], ":") {
parts := strings.Split(fileInfo[i], ":")
if len(parts) != 2 {
log.Fatalf("invalid syntax. Use 'File:lines'")
}
inputFlag.Filename = parts[0]
inputFlag.LineNum = parts[1]
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("invalid syntax. Use 'File:lines'")
}
}
}
} else {
if cmd.Flags().Changed("line") || cmd.Flags().Changed("start-label") && cmd.Flags().Changed("end-label") {
inputFlag.Filename = FileToRead
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("Not specified what you want to modify: add -l flag or -s and -e flags")
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the ReadFlags function.

The ReadFlags function reads and processes the flags provided by the user.

+// ReadFlags reads and processes the flags provided by the user.
 func ReadFlags(cmd *cobra.Command) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ReadFlags(cmd *cobra.Command) {
if Tui {
currentDir, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting current working directory: %v\n", err)
os.Exit(1)
}
// Initialize your model with the current directory
model := tui.Model{
State: "FileSelection",
FilesSelector: modelutils.InitialModel(currentDir, 20),
}
clearScreen()
// Bubble Tea program
p := tea.NewProgram(model)
// Start the program
if _, err := p.Run(); err != nil {
os.Exit(1)
}
clearScreen()
} else {
if strings.Contains(FileToRead, ",") {
if cmd.Flags().Changed("line") {
fmt.Println("Warning: when passing multiple files to flag -f, don't use -l flag")
}
if cmd.Flags().Changed("start-label") && cmd.Flags().Changed("end-label") {
fileInfo := strings.Split(FileToRead, ",")
for i := 0; i < len(fileInfo); i++ {
inputFlag.Filename = fileInfo[i]
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
}
} else {
fileInfo := strings.Split(FileToRead, ",")
for i := 0; i < len(fileInfo); i++ {
if strings.Contains(fileInfo[i], ":") {
parts := strings.Split(fileInfo[i], ":")
if len(parts) != 2 {
log.Fatalf("invalid syntax. Use 'File:lines'")
}
inputFlag.Filename = parts[0]
inputFlag.LineNum = parts[1]
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("invalid syntax. Use 'File:lines'")
}
}
}
} else {
if cmd.Flags().Changed("line") || cmd.Flags().Changed("start-label") && cmd.Flags().Changed("end-label") {
inputFlag.Filename = FileToRead
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("Not specified what you want to modify: add -l flag or -s and -e flags")
}
}
}
}
// ReadFlags reads and processes the flags provided by the user.
func ReadFlags(cmd *cobra.Command) {
if Tui {
currentDir, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting current working directory: %v\n", err)
os.Exit(1)
}
// Initialize your model with the current directory
model := tui.Model{
State: "FileSelection",
FilesSelector: modelutils.InitialModel(currentDir, 20),
}
clearScreen()
// Bubble Tea program
p := tea.NewProgram(model)
// Start the program
if _, err := p.Run(); err != nil {
os.Exit(1)
}
clearScreen()
} else {
if strings.Contains(FileToRead, ",") {
if cmd.Flags().Changed("line") {
fmt.Println("Warning: when passing multiple files to flag -f, don't use -l flag")
}
if cmd.Flags().Changed("start-label") && cmd.Flags().Changed("end-label") {
fileInfo := strings.Split(FileToRead, ",")
for i := 0; i < len(fileInfo); i++ {
inputFlag.Filename = fileInfo[i]
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
}
} else {
fileInfo := strings.Split(FileToRead, ",")
for i := 0; i < len(fileInfo); i++ {
if strings.Contains(fileInfo[i], ":") {
parts := strings.Split(fileInfo[i], ":")
if len(parts) != 2 {
log.Fatalf("invalid syntax. Use 'File:lines'")
}
inputFlag.Filename = parts[0]
inputFlag.LineNum = parts[1]
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("invalid syntax. Use 'File:lines'")
}
}
}
} else {
if cmd.Flags().Changed("line") || cmd.Flags().Changed("start-label") && cmd.Flags().Changed("end-label") {
inputFlag.Filename = FileToRead
if err := modfile.ChangeFile(inputFlag); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("Not specified what you want to modify: add -l flag or -s and -e flags")
}
}
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between d6ddccf and 2421789.

Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
Files selected for processing (3)
  • go.mod (1 hunks)
  • utils/tui/modelutils/option_test.go (1 hunks)
  • utils/tui/modelutils/text_test.go (1 hunks)
Files skipped from review due to trivial changes (1)
  • utils/tui/modelutils/option_test.go
Additional comments not posted (22)
go.mod (15)

6-6: Approved: Dependency github.com/adrg/xdg v0.4.0.

This dependency is used for XDG Base Directory Specification support and the version v0.4.0 is the latest stable version.


7-7: Approved: Dependency github.com/charmbracelet/bubbletea v0.26.4.

This dependency is used for building rich terminal user interfaces and the version v0.26.4 is the latest stable version.


8-8: Approved: Dependency github.com/charmbracelet/lipgloss v0.10.0.

This dependency is used for styling terminal user interfaces and the version v0.10.0 is the latest stable version.


10-10: Approved: Dependency github.com/charmbracelet/wish v1.4.0.

This dependency is used for SSH server functionalities and the version v1.4.0 is the latest stable version.


11-11: Approved: Dependency github.com/creack/pty v1.1.21.

This dependency is used for pseudo-terminal functionalities and the version v1.1.21 is the latest stable version.


12-12: Approved: Dependency github.com/spf13/cobra v1.8.0.

This dependency is used for creating powerful modern CLI applications and the version v1.8.0 is the latest stable version.


13-13: Approved: Dependency github.com/spf13/pflag v1.0.5.

This dependency is used for POSIX/GNU-style flag parsing and the version v1.0.5 is the latest stable version.


14-14: Approved: Dependency github.com/stretchr/testify v1.9.0.

This dependency is used for writing unit tests and the version v1.9.0 is the latest stable version.


15-15: Approved: Dependency golang.org/x/term v0.21.0.

This dependency is used for terminal manipulation and the version v0.21.0 is the latest stable version.


20-20: Approved: Dependency github.com/aymanbagabas/go-osc52/v2 v2.0.1.

This dependency is used for OSC52 escape sequences and the version v2.0.1 is the latest stable version.


21-21: Approved: Dependency github.com/charmbracelet/keygen v0.5.0.

This dependency is used for generating SSH keys and the version v0.5.0 is the latest stable version.


22-22: Approved: Dependency github.com/charmbracelet/log v0.4.0.

This dependency is used for logging and the version v0.4.0 is the latest stable version.


23-23: Approved: Dependency github.com/charmbracelet/x/ansi v0.1.2.

This dependency is used for ANSI escape sequences and the version v0.1.2 is the latest stable version.


9-9: Verify: Dependency github.com/charmbracelet/ssh v0.0.0-20240604154955-a40c6a0d028f.

The version v0.0.0-20240604154955-a40c6a0d028f is a pseudo-version indicating a specific commit. Verify the commit to ensure it is stable.

Verification successful

Verified: Dependency github.com/charmbracelet/ssh v0.0.0-20240604154955-a40c6a0d028f

The commit a40c6a0d028f6cca7a08d5b513e1640de8025106 exists and is the current HEAD of the master branch, indicating it is stable.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the commit for `github.com/charmbracelet/ssh`.

# Test: Check the commit details. Expect: Commit exists and is stable.
git ls-remote https://github.com/charmbracelet/ssh.git | grep a40c6a0d028f

Length of output: 181


19-19: Verify: Dependency github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be.

The version v0.0.0-20200514113438-38f4b401e2be is a pseudo-version indicating a specific commit. Verify the commit to ensure it is stable.

Verification successful

Verified: Dependency github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be.

The commit 38f4b401e2be5955e3e00b843d96e3c406f5094d exists and is the HEAD of the master branch, confirming its stability and appropriateness.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the commit for `github.com/anmitsu/go-shlex`.

# Test: Check the commit details. Expect: Commit exists and is stable.
git ls-remote https://github.com/anmitsu/go-shlex.git | grep 38f4b401e2be

Length of output: 180

utils/tui/modelutils/text_test.go (7)

13-16: Approved: Test setInput.

The function correctly sets the input for LabelInput.


17-29: Approved: Test stripANSI.

The function correctly removes ANSI color codes and trims whitespace.


31-55: Approved: Test TestNewLabelInput.

The test correctly checks the initialization of LabelInput.


57-61: Approved: Test TestInitLabelInput.

The test correctly checks the initialization command of LabelInput.


62-148: Approved: Test TestUpdateLabelInput.

The test correctly checks the update functionality of LabelInput with various messages.


150-191: Approved: Test TestViewLabelInput.

The test correctly checks the view functionality of LabelInput.


194-249: Approved: Test TestValidateInput.

The test correctly checks the validation of input for LabelInput.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 2421789 and 957b226.

Files selected for processing (3)
  • utils/tui/modelutils/file_test.go (1 hunks)
  • utils/tui/modelutils/selectorutils.go (1 hunks)
  • utils/tui/modelutils/selectorutils_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • utils/tui/modelutils/selectorutils.go
Additional comments not posted (7)
utils/tui/modelutils/selectorutils_test.go (5)

12-22: LGTM! Well-structured test cases for IsDirectory.

The test covers both existing and non-existing paths, ensuring robust validation.


24-63: LGTM! Comprehensive test cases for GetParentDirectory.

The test covers valid directories, root directories, and Windows-specific scenarios, ensuring thorough validation.


66-93: LGTM! Well-structured test cases for GetPathOfEntry.

The test covers both existing and non-existing entries, ensuring robust validation.


95-117: LGTM! Well-structured test cases for moveToNextDir.

The test covers both valid and non-existing directories, ensuring robust validation.


119-149: LGTM! Comprehensive test cases for moveToPreviousDir.

The test covers valid directories and root directories, ensuring thorough validation.

utils/tui/modelutils/file_test.go (2)

12-131: LGTM! Well-structured and comprehensive test cases for FilesSelector.

The test covers a wide range of scenarios, ensuring robust validation of the FilesSelector struct.


133-215: LGTM! Well-structured and comprehensive test cases for FilesSelector view rendering.

The test covers a wide range of scenarios, ensuring robust validation of the view rendering of the FilesSelector struct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 957b226 and 3e17b26.

Files selected for processing (2)
  • utils/tui/model.go (1 hunks)
  • utils/tui/modelutils/file_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • utils/tui/modelutils/file_test.go
Additional context used
golangci-lint
utils/tui/model.go

[warning] 190-190: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


83-83: unnecessary leading newline

(whitespace)


121-121: unnecessary trailing newline

(whitespace)


133-133: unnecessary trailing newline

(whitespace)


194-194: unnecessary trailing newline

(whitespace)

Additional comments not posted (5)
utils/tui/model.go (5)

1-11: LGTM!

The package declaration and imports are appropriate and necessary for the functionality provided in the file.


13-29: LGTM!

The Model struct is well-defined and includes necessary fields for managing the application's state, file paths, actions, labels, and error handling.


38-41: LGTM!

The Init method correctly initializes the FilesSelector model.


227-247: LGTM!

The View method correctly handles different states to render the appropriate view.


284-299: Add a comment for the exported function AbsToRel.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // AbsToRel converts an absolute path to a relative path.
  func AbsToRel(absPath string) (string, error) {

Likely invalid or redundant comment.

Comment on lines 31 to 36
// applyChangesMsg represents a message indicating that changes have been applied.
type applyChangesMsg struct {
err error
}

var counter int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving the counter variable inside the Model struct.

The applyChangesMsg type is appropriate for handling the application of changes. However, defining the counter variable globally may lead to issues with concurrency and state management. Consider moving it inside the Model struct.

- var counter int
+ // Counter for tracking file processing
+ Counter int

Committable suggestion was skipped due to low confidence.

Comment on lines 43 to 225
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
counter = 0
} else {
counter++
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[counter]), m.SpeedSelector.Selected)

}
}
return m, cmd
case "Fast mode":
newActionSelector, cmd := m.ActionSelector.Update(msg)
m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
if m.ActionSelector.Back {
if len(m.Files) == 1 {
m.State = "FileSelection"
m.FilesSelector.Done = false
} else {
m.SpeedSelector.Done = false
m.SpeedSelector.Selected = ""
m.State = "ModeSelection"
}
}
if m.ActionSelector.Done {
for i := 0; i < len(m.Files); i++ {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
}
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput("")
}
return m, cmd
}

case "LabelInput":
switch m.SpeedSelector.Selected {
case "Slow mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
if len(m.Labels) == 0 {
counter = len(m.Files) - 1
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = m.Actions[:len(m.Actions)-1]
m.State = "ActionSelection"
} else {
counter--
m.LabelInput.Done = false
m.Labels = m.Labels[:len(m.Labels)-1]
m.LabelType = m.LabelType[:len(m.LabelType)-1]
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[counter]))
}
}
if m.LabelInput.Done {
if m.LabelInput.Error != nil {
m.Error = m.LabelInput.Error
return m, tea.Quit
}
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
if len(m.Labels) == len(m.Files) {
m.State = "ApplyChanges"
return m, m.applyChanges()
} else {
counter++
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[counter]))

}
}
return m, cmd
case "Fast mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = nil
m.State = "ActionSelection"
}
if m.LabelInput.Done {
for i := 0; i < len(m.Files); i++ {
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
}
m.State = "ApplyChanges"
return m, m.applyChanges()
}
return m, cmd
}

case "ApplyChanges":
return m, m.applyChanges()

case "Final":
return m, nil
}

return m, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Address inefficiencies and unnecessary assignments in the Update method.

There are some inefficiencies and unnecessary assignments in the Update function. Additionally, according to Go conventions, exported methods should have a comment explaining their purpose.

+ // Update updates the model based on messages.
  func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if m.Error != nil {
        // If an error occurred, display the error and quit
        return m, tea.Quit
    }

    switch msg := msg.(type) {
    case applyChangesMsg:
        if msg.err != nil {
            m.Error = msg.err
        }
        m.State = "Final"
        return m, nil

    case tea.KeyMsg:
        if m.State == "Final" {
            return m, tea.Quit
        }
    }

    switch m.State {
    case "FileSelection":
        newFilesSelector, cmd := m.FilesSelector.Update(msg)
        m.FilesSelector = newFilesSelector.(modelutils.FilesSelector)
        if m.FilesSelector.Done {
            if m.FilesSelector.Error != nil {
                m.Error = m.FilesSelector.Error
                return m, tea.Quit
            }
            m.Files = m.FilesSelector.FilesPath
            if len(m.Files) == 1 {
                m.SpeedSelector = modelutils.ModeSelector{
                    File:     m.Files[0],
                    Choices:  []string{"Fast mode", "Slow mode"},
                    Selected: "Fast mode",
                    Speed:    "",
                }
                m.State = "ActionSelection"
                m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[0]), m.SpeedSelector.Selected)
            } else {
                m.State = "ModeSelection"
                m.SpeedSelector = modelutils.NewModeSelector([]string{"Fast mode", "Slow mode"}, "", "")
            }
        }
        return m, cmd

    case "ModeSelection":
        newSpeedSelector, cmd := m.SpeedSelector.Update(msg)
        m.SpeedSelector = newSpeedSelector.(modelutils.ModeSelector)
        if m.SpeedSelector.Back {
            m.State = "FileSelection"
            m.FilesSelector.Done = false
        }
        if m.SpeedSelector.Done {
            m.State = "ActionSelection"
            m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[0]), m.SpeedSelector.Selected)
        }
        return m, cmd

    case "ActionSelection":
        switch m.SpeedSelector.Selected {
        case "Slow mode":
            newActionSelector, cmd := m.ActionSelector.Update(msg)
            m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
            if m.ActionSelector.Back {
                if len(m.Actions) == 0 {
                    m.SpeedSelector.Done = false
                    m.SpeedSelector.Selected = ""
                    m.State = "ModeSelection"
                } else {
                    m.Counter--
                    m.ActionSelector.Done = false
                    m.Actions = m.Actions[:len(m.Actions)-1]
                    m.State = "ActionSelection"
                    m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[m.Counter]), m.SpeedSelector.Selected)
                }
            }
            if m.ActionSelector.Done {
                m.Actions = append(m.Actions, m.ActionSelector.Selected)
                if len(m.Actions) == len(m.Files) {
                    m.State = "LabelInput"
                    m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
                    m.Counter = 0
                } else {
                    m.Counter++
                    m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[m.Counter]), m.SpeedSelector.Selected)
                }
            }
            return m, cmd
        case "Fast mode":
            newActionSelector, cmd := m.ActionSelector.Update(msg)
            m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
            if m.ActionSelector.Back {
                if len(m.Files) == 1 {
                    m.State = "FileSelection"
                    m.FilesSelector.Done = false
                } else {
                    m.SpeedSelector.Done = false
                    m.SpeedSelector.Selected = ""
                    m.State = "ModeSelection"
                }
            }
            if m.ActionSelector.Done {
                for i := 0; i < len(m.Files); i++ {
                    m.Actions = append(m.Actions, m.ActionSelector.Selected)
                }
                m.State = "LabelInput"
                m.LabelInput = modelutils.NewLabelInput("")
            }
            return m, cmd
        }

    case "LabelInput":
        switch m.SpeedSelector.Selected {
        case "Slow mode":
            newLabelInput, cmd := m.LabelInput.Update(msg)
            m.LabelInput = newLabelInput.(modelutils.LabelInput)
            if m.LabelInput.Back {
                if len(m.Labels) == 0 {
                    m.Counter = len(m.Files) - 1
                    m.ActionSelector.Done = false
                    m.ActionSelector.Selected = ""
                    m.Actions = m.Actions[:len(m.Actions)-1]
                    m.State = "ActionSelection"
                } else {
                    m.Counter--
                    m.LabelInput.Done = false
                    m.Labels = m.Labels[:len(m.Labels)-1]
                    m.LabelType = m.LabelType[:len(m.LabelType)-1]
                    m.State = "LabelInput"
                    m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[m.Counter]))
                }
            }
            if m.LabelInput.Done {
                if m.LabelInput.Error != nil {
                    m.Error = m.LabelInput.Error
                    return m, tea.Quit
                }
                m.Labels = append(m.Labels, m.LabelInput.Input)
                m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
                if len(m.Labels) == len(m.Files) {
                    m.State = "ApplyChanges"
                    return m, m.applyChanges()
                } else {
                    m.Counter++
                    m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[m.Counter]))
                }
            }
            return m, cmd
        case "Fast mode":
            newLabelInput, cmd := m.LabelInput.Update(msg)
            m.LabelInput = newLabelInput.(modelutils.LabelInput)
            if m.LabelInput.Back {
                m.ActionSelector.Done = false
                m.ActionSelector.Selected = ""
                m.Actions = nil
                m.State = "ActionSelection"
            }
            if m.LabelInput.Done {
                for i := 0; i < len(m.Files); i++ {
                    m.Labels = append(m.Labels, m.LabelInput.Input)
                    m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
                }
                m.State = "ApplyChanges"
                return m, m.applyChanges()
            }
            return m, cmd
        }

    case "ApplyChanges":
        return m, m.applyChanges()

    case "Final":
        return m, nil
    }

    return m, nil
  }

Committable suggestion was skipped due to low confidence.

Tools
golangci-lint

[warning] 190-190: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


83-83: unnecessary leading newline

(whitespace)


121-121: unnecessary trailing newline

(whitespace)


133-133: unnecessary trailing newline

(whitespace)


194-194: unnecessary trailing newline

(whitespace)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 3e17b26 and 9666295.

Files selected for processing (1)
  • utils/tui/model_test.go (1 hunks)
Additional comments not posted (27)
utils/tui/model_test.go (27)

14-19: Good test coverage for initialization.

The test ensures that the Init method of the Model struct works correctly.


30-49: Good transition test from FileSelection to ModeSelection.

The test verifies that the Model transitions correctly from FileSelection to ModeSelection state.


50-67: Good transition test from FileSelection to ActionSelection.

The test verifies that the Model transitions correctly from FileSelection to ActionSelection state.


68-80: Good test for handling no file selection.

The test ensures that the Model remains in FileSelection state when no file is selected.


81-94: Good transition test from ModeSelection to ActionSelection.

The test verifies that the Model transitions correctly from ModeSelection to ActionSelection state.


95-108: Good test for handling ActionSelection state.

The test ensures that the Model remains in ActionSelection state when the action is selected again.


109-122: Good transition test from ActionSelection to LabelInput (fast mode).

The test verifies that the Model transitions correctly from ActionSelection to LabelInput state in fast mode.


123-138: Good transition test from ActionSelection to LabelInput (slow mode).

The test verifies that the Model transitions correctly from ActionSelection to LabelInput state in slow mode.


139-154: Good test for handling LabelInput state.

The test ensures that the Model remains in LabelInput state when the label input is updated.


155-172: Good transition test from ModeSelection to FileSelection.

The test verifies that the Model transitions correctly from ModeSelection to FileSelection state.


173-189: Good transition test from ActionSelection to FileSelection.

The test verifies that the Model transitions correctly from ActionSelection to FileSelection state.


190-205: Good transition test from ActionSelection to ModeSelection (fast mode).

The test verifies that the Model transitions correctly from ActionSelection to ModeSelection state in fast mode.


206-220: Good transition test from ActionSelection to ModeSelection (slow mode).

The test verifies that the Model transitions correctly from ActionSelection to ModeSelection state in slow mode.


221-236: Good test for handling ActionSelection state.

The test ensures that the Model remains in ActionSelection state when the action is selected again.


239-252: Good transition test from LabelInput to ActionSelection (fast mode).

The test verifies that the Model transitions correctly from LabelInput to ActionSelection state in fast mode.


253-270: Good transition test from LabelInput to ActionSelection (slow mode).

The test verifies that the Model transitions correctly from LabelInput to ActionSelection state in slow mode.


271-286: Good test for handling LabelInput state.

The test ensures that the Model remains in LabelInput state when the label input is updated.


352-361: Good test for successful changes.

The test verifies that the applyChanges method works correctly when changes are applied successfully.


362-370: Good test for handling multiple files.

The test ensures that the applyChanges method works correctly when changes are applied to multiple files.


371-380: Good test for error handling.

The test verifies that the applyChanges method handles errors correctly when applying changes.


424-429: Good test for FileSelection view.

The test verifies that the View method renders the correct view for the FileSelection state.


430-434: Good test for ModeSelection view.

The test ensures that the View method renders the correct view for the ModeSelection state.


435-444: Good test for ActionSelection view.

The test verifies that the View method renders the correct view for the ActionSelection state.


445-447: Good test for LabelInput view.

The test ensures that the View method renders the correct view for the LabelInput state.


448-450: Good test for Final view with error.

The test verifies that the View method renders the correct view for the Final state when an error occurs.


451-455: Good test for Final view without error.

The test ensures that the View method renders the correct view for the Final state when no error occurs.


468-483: Good utility function for creating temporary files.

The function handles file creation, writing, and cleanup correctly.

Filippo Trotter added 2 commits July 9, 2024 12:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 9666295 and 0d44323.

Files selected for processing (3)
  • utils/commenter/commenter.go (1 hunks)
  • utils/server/server.go (1 hunks)
  • utils/tui/model.go (1 hunks)
Additional context used
golangci-lint
utils/server/server.go

[warning] 34-34: exported: exported function StartServer should have comment or be unexported

(revive)

utils/tui/model.go

[warning] 183-183: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


81-81: unnecessary leading newline

(whitespace)


118-118: unnecessary trailing newline

(whitespace)


128-128: unnecessary trailing newline

(whitespace)


186-186: unnecessary trailing newline

(whitespace)

Additional comments not posted (4)
utils/commenter/commenter.go (1)

8-15: LGTM!

The function implementation looks correct and efficient.

utils/tui/model.go (3)

36-39: LGTM!

The function implementation looks correct and efficient.


219-239: LGTM!

The function implementation looks correct and efficient.


102-131: Address inefficiencies and unnecessary assignments in the Update method.

There are some inefficiencies and unnecessary assignments in the Update function.

-  counter := 1
-  newActionSelector, cmd := m.ActionSelector.Update(msg)
-  m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
-  if m.ActionSelector.Done {
-    m.Actions = append(m.Actions, m.ActionSelector.Selected)
-    if len(m.Actions) == len(m.Files) {
-      m.State = "LabelInput"
-      m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
-    } else {
-      m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[counter]), m.SpeedSelector.Selected)
-      counter++
-    }
-  }
+  newActionSelector, cmd := m.ActionSelector.Update(msg)
+  m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
+  if m.ActionSelector.Done {
+    m.Actions = append(m.Actions, m.ActionSelector.Selected)
+    if len(m.Actions) == len(m.Files) {
+      m.State = "LabelInput"
+      m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
+    } else {
+      m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[len(m.Actions)]), m.SpeedSelector.Selected)
+    }
+  }

Likely invalid or redundant comment.

Tools
golangci-lint

118-118: unnecessary trailing newline

(whitespace)


128-128: unnecessary trailing newline

(whitespace)

Comment on lines 41 to 56
// ToggleComments toggles comments on or off for the given line based on its current state.
func ToggleComments(line string, char string) string {
trimmedLine := strings.TrimSpace(line)

//just for html
if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
return Uncomment(line, char)
} else if char == "<!-- -->" {
return Comment(line, char)
}

if strings.HasPrefix(trimmedLine, char) {
return Uncomment(line, char)
}
return Comment(line, char)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify the handling of HTML comments.

The special handling for HTML comments can be improved for readability.

-	if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
-		return Uncomment(line, char)
-	} else if char == "<!-- -->" {
-		return Comment(line, char)
-	}
+	if char == "<!-- -->" {
+		if strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
+			return Uncomment(line, char)
+		}
+		return Comment(line, char)
+	}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// ToggleComments toggles comments on or off for the given line based on its current state.
func ToggleComments(line string, char string) string {
trimmedLine := strings.TrimSpace(line)
//just for html
if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
return Uncomment(line, char)
} else if char == "<!-- -->" {
return Comment(line, char)
}
if strings.HasPrefix(trimmedLine, char) {
return Uncomment(line, char)
}
return Comment(line, char)
}
// ToggleComments toggles comments on or off for the given line based on its current state.
func ToggleComments(line string, char string) string {
trimmedLine := strings.TrimSpace(line)
//just for html
if char == "<!-- -->" {
if strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
return Uncomment(line, char)
}
return Comment(line, char)
}
if strings.HasPrefix(trimmedLine, char) {
return Uncomment(line, char)
}
return Comment(line, char)
}

Comment on lines 17 to 39
// Uncomment removes a comment character or string from the beginning of the given line, if present.
func Uncomment(line string, char string) string {
trimmedLine := strings.TrimSpace(line)

//just for html
if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
line = strings.Replace(line, "<!-- ", "", 1)
line = strings.Replace(line, "<!--", "", 1)
line = strings.Replace(line, " "+"-->", "", 1)
line = strings.Replace(line, "-->", "", 1)

return line
}

if strings.HasPrefix(trimmedLine, char) {
// Check for both `//` and `// ` prefixes.
if strings.HasPrefix(trimmedLine, char+" ") {
return strings.Replace(line, char+" ", "", 1)
}
return strings.Replace(line, char, "", 1)
}
return line
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify the handling of HTML comments.

The detailed handling for HTML comments can be simplified for better readability and maintainability.

-	if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
-		line = strings.Replace(line, "<!-- ", "", 1)
-		line = strings.Replace(line, "<!--", "", 1)
-		line = strings.Replace(line, " "+"-->", "", 1)
-		line = strings.Replace(line, "-->", "", 1)
-		return line
-	}
+	if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
+		line = strings.TrimPrefix(line, "<!-- ")
+		line = strings.TrimSuffix(line, " -->")
+		return line
+	}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Uncomment removes a comment character or string from the beginning of the given line, if present.
func Uncomment(line string, char string) string {
trimmedLine := strings.TrimSpace(line)
//just for html
if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
line = strings.Replace(line, "<!-- ", "", 1)
line = strings.Replace(line, "<!--", "", 1)
line = strings.Replace(line, " "+"-->", "", 1)
line = strings.Replace(line, "-->", "", 1)
return line
}
if strings.HasPrefix(trimmedLine, char) {
// Check for both `//` and `// ` prefixes.
if strings.HasPrefix(trimmedLine, char+" ") {
return strings.Replace(line, char+" ", "", 1)
}
return strings.Replace(line, char, "", 1)
}
return line
}
// Uncomment removes a comment character or string from the beginning of the given line, if present.
func Uncomment(line string, char string) string {
trimmedLine := strings.TrimSpace(line)
//just for html
if char == "<!-- -->" && strings.HasPrefix(trimmedLine, "<!--") && strings.HasSuffix(trimmedLine, "-->") {
line = strings.TrimPrefix(line, "<!-- ")
line = strings.TrimSuffix(line, " -->")
return line
}
if strings.HasPrefix(trimmedLine, char) {
// Check for both `//` and `// ` prefixes.
if strings.HasPrefix(trimmedLine, char+" ") {
return strings.Replace(line, char+" ", "", 1)
}
return strings.Replace(line, char, "", 1)
}
return line
}

Comment on lines +55 to +65
func(next ssh.Handler) ssh.Handler {
return func(s ssh.Session) {
command := s.Command()
if len(command) < 2 {
wish.Println(s, "Usage tgcom <directory>")
next(s)
return
}
dir = command[1]
next(s)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve readability of command handling logic.

The command handling logic can be improved for better readability and maintainability.

-	func(next ssh.Handler) ssh.Handler {
-		return func(s ssh.Session) {
-			command := s.Command()
-			if len(command) < 2 {
-				wish.Println(s, "Usage tgcom <directory>")
-				next(s)
-				return
-			}
-			dir = command[1]
-			next(s)
-		}
-	},
+	func(next ssh.Handler) ssh.Handler {
+		return func(s ssh.Session) {
+			command := s.Command()
+			if len(command) < 2 {
+				wish.Println(s, "Usage tgcom <directory>")
+				next(s)
+				return
+			}
+			dir = command[1]
+			next(s)
+		}
+	},

Committable suggestion was skipped due to low confidence.

Comment on lines +34 to +93
func StartServer() {
withHostKey := wish.WithHostKeyPath(pathHostKey)
if pem, ok := os.LookupEnv(envHostKey); ok {
withHostKey = wish.WithHostKeyPEM([]byte(pem))
}
srv, err := wish.NewServer(
wish.WithAddress(":2222"),
wish.WithMiddleware(
bm.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
pty, _, _ := s.Pty()
// Initialize the file selector model with the directory argument
model := tui.Model{
State: "FileSelection",
FilesSelector: modelutils.InitialModel(dir, pty.Window.Height-5), // Initialize the FilesSelector model with window height
}
if model.Error != nil {
wish.Println(s, model.Error.Error())
return nil, nil
}
return model, teaOptions
}),
func(next ssh.Handler) ssh.Handler {
return func(s ssh.Session) {
command := s.Command()
if len(command) < 2 {
wish.Println(s, "Usage tgcom <directory>")
next(s)
return
}
dir = command[1]
next(s)
}
},
activeterm.Middleware(),
lm.Middleware(),
),

withHostKey,
)
if err != nil {
log.Fatalf("could not create server: %s", err)
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

log.Printf("starting server: %s", srv.Addr)
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("server returned an error: %s", err)
}
}()

<-done
log.Println("stopping server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("could not shutdown server gracefully: %s", err)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function StartServer.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // StartServer initializes and starts an SSH server with directory browsing capabilities and command handling.
  func StartServer() {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func StartServer() {
withHostKey := wish.WithHostKeyPath(pathHostKey)
if pem, ok := os.LookupEnv(envHostKey); ok {
withHostKey = wish.WithHostKeyPEM([]byte(pem))
}
srv, err := wish.NewServer(
wish.WithAddress(":2222"),
wish.WithMiddleware(
bm.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
pty, _, _ := s.Pty()
// Initialize the file selector model with the directory argument
model := tui.Model{
State: "FileSelection",
FilesSelector: modelutils.InitialModel(dir, pty.Window.Height-5), // Initialize the FilesSelector model with window height
}
if model.Error != nil {
wish.Println(s, model.Error.Error())
return nil, nil
}
return model, teaOptions
}),
func(next ssh.Handler) ssh.Handler {
return func(s ssh.Session) {
command := s.Command()
if len(command) < 2 {
wish.Println(s, "Usage tgcom <directory>")
next(s)
return
}
dir = command[1]
next(s)
}
},
activeterm.Middleware(),
lm.Middleware(),
),
withHostKey,
)
if err != nil {
log.Fatalf("could not create server: %s", err)
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
log.Printf("starting server: %s", srv.Addr)
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("server returned an error: %s", err)
}
}()
<-done
log.Println("stopping server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("could not shutdown server gracefully: %s", err)
}
}
// StartServer initializes and starts an SSH server with directory browsing capabilities and command handling.
func StartServer() {
withHostKey := wish.WithHostKeyPath(pathHostKey)
if pem, ok := os.LookupEnv(envHostKey); ok {
withHostKey = wish.WithHostKeyPEM([]byte(pem))
}
srv, err := wish.NewServer(
wish.WithAddress(":2222"),
wish.WithMiddleware(
bm.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
pty, _, _ := s.Pty()
// Initialize the file selector model with the directory argument
model := tui.Model{
State: "FileSelection",
FilesSelector: modelutils.InitialModel(dir, pty.Window.Height-5), // Initialize the FilesSelector model with window height
}
if model.Error != nil {
wish.Println(s, model.Error.Error())
return nil, nil
}
return model, teaOptions
}),
func(next ssh.Handler) ssh.Handler {
return func(s ssh.Session) {
command := s.Command()
if len(command) < 2 {
wish.Println(s, "Usage tgcom <directory>")
next(s)
return
}
dir = command[1]
next(s)
}
},
activeterm.Middleware(),
lm.Middleware(),
),
withHostKey,
)
if err != nil {
log.Fatalf("could not create server: %s", err)
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
log.Printf("starting server: %s", srv.Addr)
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("server returned an error: %s", err)
}
}()
<-done
log.Println("stopping server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("could not shutdown server gracefully: %s", err)
}
}
Tools
golangci-lint

[warning] 34-34: exported: exported function StartServer should have comment or be unexported

(revive)

Comment on lines 276 to 291
// Helper function to convert absolute path to relative path
func AbsToRel(absPath string) (string, error) {
// Get the current working directory
currentDir, err := filepath.Abs(".")
if err != nil {
return "", fmt.Errorf("failed to get the current directory: %w", err)
}

// Convert the absolute path to a relative path
relPath, err := filepath.Rel(currentDir, absPath)
if err != nil {
return "", fmt.Errorf("failed to convert to relative path: %w", err)
}

return relPath, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function AbsToRel.

According to Go conventions, exported functions should have a comment explaining their purpose.

+ // AbsToRel converts an absolute path to a relative path.
  func AbsToRel(absPath string) (string, error) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Helper function to convert absolute path to relative path
func AbsToRel(absPath string) (string, error) {
// Get the current working directory
currentDir, err := filepath.Abs(".")
if err != nil {
return "", fmt.Errorf("failed to get the current directory: %w", err)
}
// Convert the absolute path to a relative path
relPath, err := filepath.Rel(currentDir, absPath)
if err != nil {
return "", fmt.Errorf("failed to convert to relative path: %w", err)
}
return relPath, nil
}
// AbsToRel converts an absolute path to a relative path.
func AbsToRel(absPath string) (string, error) {
// Get the current working directory
currentDir, err := filepath.Abs(".")
if err != nil {
return "", fmt.Errorf("failed to get the current directory: %w", err)
}
// Convert the absolute path to a relative path
relPath, err := filepath.Rel(currentDir, absPath)
if err != nil {
return "", fmt.Errorf("failed to convert to relative path: %w", err)
}
return relPath, nil
}

Comment on lines 241 to 274
// applyChanges applies changes to selected files based on user inputs
func (m *Model) applyChanges() tea.Cmd {
return func() tea.Msg {
for i := 0; i < len(m.Files); i++ {
currentFilePath, err := AbsToRel(m.Files[i])
if err != nil {
return applyChangesMsg{err: fmt.Errorf("failed to convert to relative path: %w", err)}
}
if !m.LabelType[i] {
conf := modfile.Config{
Filename: currentFilePath,
LineNum: m.Labels[i],
Action: m.Actions[i],
}
err = modfile.ChangeFile(conf)
} else {
parts := strings.Split(m.Labels[i], ";")
conf := modfile.Config{
Filename: currentFilePath,
StartLabel: parts[0],
EndLabel: parts[1],
Action: m.Actions[i],
}
err = modfile.ChangeFile(conf)
}

if err != nil {
return applyChangesMsg{err: fmt.Errorf("failed to apply changes to file %s: %w", m.Files[i], err)}
}
}

return applyChangesMsg{err: nil}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize the applyChanges method.

The method is well-structured but can be optimized by removing unnecessary else blocks.

  func (m *Model) applyChanges() tea.Cmd {
    return func() tea.Msg {
        for i := 0; i < len(m.Files); i++ {
            currentFilePath, err := AbsToRel(m.Files[i])
            if err != nil {
                return applyChangesMsg{err: fmt.Errorf("failed to convert to relative path: %w", err)}
            }
            if !m.LabelType[i] {
                conf := modfile.Config{
                    Filename: currentFilePath,
                    LineNum:  m.Labels[i],
                    Action:   m.Actions[i],
                }
                err = modfile.ChangeFile(conf)
            } else {
                parts := strings.Split(m.Labels[i], ";")
                conf := modfile.Config{
                    Filename:   currentFilePath,
                    StartLabel: parts[0],
                    EndLabel:   parts[1],
                    Action:     m.Actions[i],
                }
                err = modfile.ChangeFile(conf)
            }

            if err != nil {
                return applyChangesMsg{err: fmt.Errorf("failed to apply changes to file %s: %w", m.Files[i], err)}
            }
        }

        return applyChangesMsg{err: nil}
    }
  }

Committable suggestion was skipped due to low confidence.

Comment on lines 41 to 217
}
}
if m.ActionSelector.Done {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
if len(m.Actions) == len(m.Files) {
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
} else {
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[len(m.Actions)]), m.SpeedSelector.Selected)

}
}
return m, cmd
case "Fast mode":
newActionSelector, cmd := m.ActionSelector.Update(msg)
m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
if m.ActionSelector.Back {
if len(m.Files) == 1 {
m.State = "FileSelection"
m.FilesSelector.Done = false
} else {
m.SpeedSelector.Done = false
m.SpeedSelector.Selected = ""
m.State = "ModeSelection"
}
}
if m.ActionSelector.Done {
for i := 0; i < len(m.Files); i++ {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
}
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput("")
}
return m, cmd
}

case "LabelInput":
switch m.SpeedSelector.Selected {
case "Slow mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
if len(m.Labels) == 0 {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = m.Actions[:len(m.Actions)-1]
m.State = "ActionSelection"
} else {
m.LabelInput.Done = false
m.Labels = m.Labels[:len(m.Labels)-1]
m.LabelType = m.LabelType[:len(m.LabelType)-1]
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[len(m.Labels)]))
}
}
if m.LabelInput.Done {
if m.LabelInput.Error != nil {
m.Error = m.LabelInput.Error
return m, tea.Quit
}
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
if len(m.Labels) == len(m.Files) {
m.State = "ApplyChanges"
return m, m.applyChanges()
} else {
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[len(m.Labels)]))

}
}
return m, cmd
case "Fast mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = nil
m.State = "ActionSelection"
}
if m.LabelInput.Done {
for i := 0; i < len(m.Files); i++ {
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
}
m.State = "ApplyChanges"
return m, m.applyChanges()
}
return m, cmd
}

case "ApplyChanges":
return m, m.applyChanges()

case "Final":
return m, nil
}

return m, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment for the exported function Update.

According to Go conventions, exported methods should have a comment explaining their purpose.

+ // Update updates the model based on messages.
  func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Update updates the model based on messages
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.Error != nil {
// If an error occurred, display the error and quit
return m, tea.Quit
}
switch msg := msg.(type) {
case applyChangesMsg:
if msg.err != nil {
m.Error = msg.err
}
m.State = "Final"
return m, nil
case tea.KeyMsg:
if m.State == "Final" {
return m, tea.Quit
}
}
switch m.State {
case "FileSelection":
newFilesSelector, cmd := m.FilesSelector.Update(msg)
m.FilesSelector = newFilesSelector.(modelutils.FilesSelector)
if m.FilesSelector.Done {
if m.FilesSelector.Error != nil {
m.Error = m.FilesSelector.Error
return m, tea.Quit
}
m.Files = m.FilesSelector.FilesPath
if len(m.Files) == 1 {
m.SpeedSelector = modelutils.ModeSelector{
File: m.Files[0],
Choices: []string{"Fast mode", "Slow mode"},
Selected: "Fast mode",
Speed: "",
}
m.State = "ActionSelection"
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[0]), m.SpeedSelector.Selected)
} else {
m.State = "ModeSelection"
m.SpeedSelector = modelutils.NewModeSelector([]string{"Fast mode", "Slow mode"}, "", "")
}
}
return m, cmd
case "ModeSelection":
newSpeedSelector, cmd := m.SpeedSelector.Update(msg)
m.SpeedSelector = newSpeedSelector.(modelutils.ModeSelector)
if m.SpeedSelector.Back {
m.State = "FileSelection"
m.FilesSelector.Done = false
}
if m.SpeedSelector.Done {
m.State = "ActionSelection"
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[0]), m.SpeedSelector.Selected)
}
return m, cmd
case "ActionSelection":
switch m.SpeedSelector.Selected {
case "Slow mode":
newActionSelector, cmd := m.ActionSelector.Update(msg)
m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
if m.ActionSelector.Back {
if len(m.Actions) == 0 {
m.SpeedSelector.Done = false
m.SpeedSelector.Selected = ""
m.State = "ModeSelection"
} else {
m.ActionSelector.Done = false
m.Actions = m.Actions[:len(m.Actions)-1]
m.State = "ActionSelection"
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[len(m.Actions)]), m.SpeedSelector.Selected)
}
}
if m.ActionSelector.Done {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
if len(m.Actions) == len(m.Files) {
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
} else {
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[len(m.Actions)]), m.SpeedSelector.Selected)
}
}
return m, cmd
case "Fast mode":
newActionSelector, cmd := m.ActionSelector.Update(msg)
m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
if m.ActionSelector.Back {
if len(m.Files) == 1 {
m.State = "FileSelection"
m.FilesSelector.Done = false
} else {
m.SpeedSelector.Done = false
m.SpeedSelector.Selected = ""
m.State = "ModeSelection"
}
}
if m.ActionSelector.Done {
for i := 0; i < len(m.Files); i++ {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
}
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput("")
}
return m, cmd
}
case "LabelInput":
switch m.SpeedSelector.Selected {
case "Slow mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
if len(m.Labels) == 0 {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = m.Actions[:len(m.Actions)-1]
m.State = "ActionSelection"
} else {
m.LabelInput.Done = false
m.Labels = m.Labels[:len(m.Labels)-1]
m.LabelType = m.LabelType[:len(m.LabelType)-1]
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[len(m.Labels)]))
}
}
if m.LabelInput.Done {
if m.LabelInput.Error != nil {
m.Error = m.LabelInput.Error
return m, tea.Quit
}
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
if len(m.Labels) == len(m.Files) {
m.State = "ApplyChanges"
return m, m.applyChanges()
} else {
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[len(m.Labels)]))
}
}
return m, cmd
case "Fast mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = nil
m.State = "ActionSelection"
}
if m.LabelInput.Done {
for i := 0; i < len(m.Files); i++ {
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
}
m.State = "ApplyChanges"
return m, m.applyChanges()
}
return m, cmd
}
case "ApplyChanges":
return m, m.applyChanges()
case "Final":
return m, nil
}
return m, nil
}
// Update updates the model based on messages.
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.Error != nil {
// If an error occurred, display the error and quit
return m, tea.Quit
}
switch msg := msg.(type) {
case applyChangesMsg:
if msg.err != nil {
m.Error = msg.err
}
m.State = "Final"
return m, nil
case tea.KeyMsg:
if m.State == "Final" {
return m, tea.Quit
}
}
switch m.State {
case "FileSelection":
newFilesSelector, cmd := m.FilesSelector.Update(msg)
m.FilesSelector = newFilesSelector.(modelutils.FilesSelector)
if m.FilesSelector.Done {
if m.FilesSelector.Error != nil {
m.Error = m.FilesSelector.Error
return m, tea.Quit
}
m.Files = m.FilesSelector.FilesPath
if len(m.Files) == 1 {
m.SpeedSelector = modelutils.ModeSelector{
File: m.Files[0],
Choices: []string{"Fast mode", "Slow mode"},
Selected: "Fast mode",
Speed: "",
}
m.State = "ActionSelection"
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[0]), m.SpeedSelector.Selected)
} else {
m.State = "ModeSelection"
m.SpeedSelector = modelutils.NewModeSelector([]string{"Fast mode", "Slow mode"}, "", "")
}
}
return m, cmd
case "ModeSelection":
newSpeedSelector, cmd := m.SpeedSelector.Update(msg)
m.SpeedSelector = newSpeedSelector.(modelutils.ModeSelector)
if m.SpeedSelector.Back {
m.State = "FileSelection"
m.FilesSelector.Done = false
}
if m.SpeedSelector.Done {
m.State = "ActionSelection"
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[0]), m.SpeedSelector.Selected)
}
return m, cmd
case "ActionSelection":
switch m.SpeedSelector.Selected {
case "Slow mode":
newActionSelector, cmd := m.ActionSelector.Update(msg)
m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
if m.ActionSelector.Back {
if len(m.Actions) == 0 {
m.SpeedSelector.Done = false
m.SpeedSelector.Selected = ""
m.State = "ModeSelection"
} else {
m.ActionSelector.Done = false
m.Actions = m.Actions[:len(m.Actions)-1]
m.State = "ActionSelection"
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[len(m.Actions)]), m.SpeedSelector.Selected)
}
}
if m.ActionSelector.Done {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
if len(m.Actions) == len(m.Files) {
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[0]))
} else {
m.ActionSelector = modelutils.NewModeSelector([]string{"toggle", "comment", "uncomment"}, filepath.Base(m.Files[len(m.Actions)]), m.SpeedSelector.Selected)
}
}
return m, cmd
case "Fast mode":
newActionSelector, cmd := m.ActionSelector.Update(msg)
m.ActionSelector = newActionSelector.(modelutils.ModeSelector)
if m.ActionSelector.Back {
if len(m.Files) == 1 {
m.State = "FileSelection"
m.FilesSelector.Done = false
} else {
m.SpeedSelector.Done = false
m.SpeedSelector.Selected = ""
m.State = "ModeSelection"
}
}
if m.ActionSelector.Done {
for i := 0; i < len(m.Files); i++ {
m.Actions = append(m.Actions, m.ActionSelector.Selected)
}
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput("")
}
return m, cmd
}
case "LabelInput":
switch m.SpeedSelector.Selected {
case "Slow mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
if len(m.Labels) == 0 {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = m.Actions[:len(m.Actions)-1]
m.State = "ActionSelection"
} else {
m.LabelInput.Done = false
m.Labels = m.Labels[:len(m.Labels)-1]
m.LabelType = m.LabelType[:len(m.LabelType)-1]
m.State = "LabelInput"
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[len(m.Labels)]))
}
}
if m.LabelInput.Done {
if m.LabelInput.Error != nil {
m.Error = m.LabelInput.Error
return m, tea.Quit
}
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
if len(m.Labels) == len(m.Files) {
m.State = "ApplyChanges"
return m, m.applyChanges()
} else {
m.LabelInput = modelutils.NewLabelInput(filepath.Base(m.Files[len(m.Labels)]))
}
}
return m, cmd
case "Fast mode":
newLabelInput, cmd := m.LabelInput.Update(msg)
m.LabelInput = newLabelInput.(modelutils.LabelInput)
if m.LabelInput.Back {
m.ActionSelector.Done = false
m.ActionSelector.Selected = ""
m.Actions = nil
m.State = "ActionSelection"
}
if m.LabelInput.Done {
for i := 0; i < len(m.Files); i++ {
m.Labels = append(m.Labels, m.LabelInput.Input)
m.LabelType = append(m.LabelType, m.LabelInput.IsLabel)
}
m.State = "ApplyChanges"
return m, m.applyChanges()
}
return m, cmd
}
case "ApplyChanges":
return m, m.applyChanges()
case "Final":
return m, nil
}
return m, nil
}
Tools
golangci-lint

[warning] 183-183: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block

(revive)


81-81: unnecessary leading newline

(whitespace)


118-118: unnecessary trailing newline

(whitespace)


128-128: unnecessary trailing newline

(whitespace)


186-186: unnecessary trailing newline

(whitespace)

@puria puria merged commit 4088ab0 into main Jul 9, 2024
1 check passed
@puria puria deleted the feat/wish branch July 9, 2024 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants