Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions internal/app/callbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ func (a *App) OpenPRDiff(group *ui.ChangesGroup, status git.FileStatus, extended
dv.OnFetchExtended = func(dv *ui.DiffViewWidget) {
a.fetchPRFileContent(dv, group.PROwner, group.PRRepo, group.PRBaseSHA, group.PRHeadSHA, status.Path)
}
// Set inline comments for this file
fileComments := github.CommentsForFile(group.Comments, status.Path)
if len(fileComments) > 0 {
dv.SetComments(fileComments)
}
}
a.FocusEditorIfEnabled()
}
Expand Down Expand Up @@ -371,6 +376,24 @@ func (a *App) ConfirmDiscard(message string, onConfirm func()) {
)
}

// updateDiffComments updates inline comments on any open diff tabs belonging to a PR group.
func (a *App) updateDiffComments(groupName string, comments []github.PRComment) {
// Find the PR group to get the file list
for _, g := range a.Changes.Groups {
if !g.IsPR || g.Name != groupName {
continue
}
for _, f := range g.Unstaged {
tabName := f.Path + " (diff)"
if dv := a.EditorGroup.DiffWidgetByTab(tabName); dv != nil {
fileComments := github.CommentsForFile(comments, f.Path)
dv.SetComments(fileComments)
}
}
break
}
}

func registerWidgetCallbacks(app *App) {
reg := app.Reg

Expand Down Expand Up @@ -512,6 +535,13 @@ func registerWidgetCallbacks(app *App) {
app.Changes.OnGroupMenu = app.ShowGroupMenu
app.Changes.OnCommit = app.CommitChanges
app.Changes.OnConfirmDiscard = app.ConfirmDiscard
app.Changes.OnAddComment = func(group *ui.ChangesGroup, body string) {
app.AddPRComment(group, body)
}
app.Changes.OnViewComment = func(comment github.PRComment) {
firstLine := strings.SplitN(comment.Body, "\n", 2)[0]
app.StatusNotify(fmt.Sprintf("@%s: %s", comment.User, firstLine))
}

app.ContentSplit.OnResize = func(height int) {
if height <= 0 {
Expand Down
35 changes: 33 additions & 2 deletions internal/app/eventloop.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,45 @@ func RunEventLoop(
})
}
groupName := fmt.Sprintf("PR #%d: %s", v.Info.Number, v.Info.Title)
app.Changes.AddPRGroup(groupName, v.URL, v.Info.Owner, v.Info.Repo, v.Info.BaseSHA, v.Info.HeadSHA, files, v.Diffs)
app.Changes.AddPRGroup(groupName, v.URL, v.Info.Owner, v.Info.Repo, v.Info.BaseSHA, v.Info.HeadSHA, v.Info.Number, files, v.Diffs)
if len(v.Comments) > 0 {
app.Changes.SetPRComments(groupName, v.Comments)
}
app.Sidebar.SetActivePanel("changes")
if !app.Sidebar.Visible {
app.ShowSidebar()
}
app.Root.SetFocus(app.Changes)
app.Sidebar.SetPanelDirty("changes", app.Changes.TotalChanges() > 0)
app.StatusNotify(fmt.Sprintf("Opened PR #%d: %s (%d files)", v.Info.Number, v.Info.Title, len(v.Info.Files)))
commentInfo := ""
if len(v.Comments) > 0 {
commentInfo = fmt.Sprintf(", %d comments", len(v.Comments))
}
app.StatusNotify(fmt.Sprintf("Opened PR #%d: %s (%d files%s)", v.Info.Number, v.Info.Title, len(v.Info.Files), commentInfo))
}
case *PrCommentAddResult:
if v.Err != nil {
app.StatusError("Failed to add comment: " + v.Err.Error())
} else {
app.StatusNotify("Comment added")
// Clear the comment input and refresh comments
for i := range app.Changes.Groups {
if app.Changes.Groups[i].Name == v.GroupName {
if app.Changes.Groups[i].CommentInput != nil {
app.Changes.Groups[i].CommentInput.Clear()
}
app.RefreshPRComments(&app.Changes.Groups[i])
break
}
}
}
case *PrCommentsRefreshResult:
if v.Err != nil {
app.StatusError("Failed to refresh comments: " + v.Err.Error())
} else {
app.Changes.SetPRComments(v.GroupName, v.Comments)
// Also update any open diff tabs with new comments
app.updateDiffComments(v.GroupName, v.Comments)
}
}
redraw()
Expand Down
1 change: 0 additions & 1 deletion internal/app/lsp_convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ type DiagnosticsResult struct {
Diagnostics []ui.Diagnostic
}


type SignatureHelpResult struct {
Label string
ParamStart int
Expand Down
76 changes: 71 additions & 5 deletions internal/app/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import (
"fmt"

"github.com/eugenioenko/ttt/internal/github"
"github.com/eugenioenko/ttt/internal/ui"

"github.com/gdamore/tcell/v2"
)

type PrFetchResult struct {
URL string
Info *github.PRInfo
Diffs map[string]string
Err error
URL string
Info *github.PRInfo
Diffs map[string]string
Comments []github.PRComment
Err error
}

type DiffContentResult struct {
Expand All @@ -22,6 +24,22 @@ type DiffContentResult struct {
Err error
}

// PrCommentAddResult carries the result of adding a PR comment.
type PrCommentAddResult struct {
GroupName string
Owner string
Repo string
Number int
Err error
}

// PrCommentsRefreshResult carries refreshed comments for a PR group.
type PrCommentsRefreshResult struct {
GroupName string
Comments []github.PRComment
Err error
}

func (a *App) FetchAndOpenPR(url string) {
owner, repo, number, err := github.ParsePRURL(url)
if err != nil {
Expand All @@ -46,6 +64,54 @@ func (a *App) FetchAndOpenPR(url string) {
}

diffs := github.SplitMultiFileDiff(diffText)
a.Screen.PostEvent(tcell.NewEventInterrupt(&PrFetchResult{URL: url, Info: info, Diffs: diffs}))

// Also fetch PR comments (non-blocking - errors here are not fatal)
comments, _ := github.FetchPRComments(owner, repo, number)

a.Screen.PostEvent(tcell.NewEventInterrupt(&PrFetchResult{URL: url, Info: info, Diffs: diffs, Comments: comments}))
}()
}

// AddPRComment adds a general comment to a PR and refreshes comments.
func (a *App) AddPRComment(group *ui.ChangesGroup, body string) {
if group == nil || group.PROwner == "" || group.PRNumber == 0 {
a.StatusError("Cannot add comment: PR info not available")
return
}
owner := group.PROwner
repo := group.PRRepo
number := group.PRNumber
groupName := group.Name

a.StatusNotify("Adding comment...")
go func() {
err := github.AddPRComment(owner, repo, number, body)
a.Screen.PostEvent(tcell.NewEventInterrupt(&PrCommentAddResult{
GroupName: groupName,
Owner: owner,
Repo: repo,
Number: number,
Err: err,
}))
}()
}

// RefreshPRComments re-fetches comments for a PR group.
func (a *App) RefreshPRComments(group *ui.ChangesGroup) {
if group == nil || group.PROwner == "" || group.PRNumber == 0 {
return
}
owner := group.PROwner
repo := group.PRRepo
number := group.PRNumber
groupName := group.Name

go func() {
comments, err := github.FetchPRComments(owner, repo, number)
a.Screen.PostEvent(tcell.NewEventInterrupt(&PrCommentsRefreshResult{
GroupName: groupName,
Comments: comments,
Err: err,
}))
}()
}
2 changes: 2 additions & 0 deletions internal/app/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ func BuildStyleMap(theme config.ThemeConfig) term.StyleMap {
applyStyleDef(&m, term.StyleGutterAdded, theme.Diff.GutterAdded)
applyStyleDef(&m, term.StyleGutterDeleted, theme.Diff.GutterDeleted)
applyStyleDef(&m, term.StyleGutterModified, theme.Diff.GutterModified)
applyStyleDef(&m, term.StyleCommentBg, theme.Diff.CommentBg)
applyStyleDef(&m, term.StyleCommentAuthor, theme.Diff.CommentAuthor)
applyStyleDef(&m, term.StyleActiveLine, theme.Editor.ActiveLine)
applyStyleDef(&m, term.StyleScrollbar, config.StyleDef{Fg: theme.Scrollbar.Bg})
applyStyleDef(&m, term.StyleScrollbarThumb, config.StyleDef{Fg: theme.Scrollbar.Fg})
Expand Down
50 changes: 25 additions & 25 deletions internal/app/widgets.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package app

import (
"os"
"path/filepath"
"strings"
"github.com/eugenioenko/ttt/internal/config"
"github.com/eugenioenko/ttt/internal/github"
"github.com/eugenioenko/ttt/internal/term"
"github.com/eugenioenko/ttt/internal/ui"
"github.com/eugenioenko/ttt/internal/view"
"github.com/eugenioenko/ttt/internal/workspace"
"os"
"path/filepath"
"strings"
)

func isPRURL(arg string) bool {
Expand Down Expand Up @@ -177,27 +177,27 @@ func BuildAppFromConfig(cfg *config.AppConfig, borders *term.BorderSet, ws *work
root.SetFocus(editorGroup)

return &App{
Root: root,
EditorGroup: editorGroup,
Sidebar: sidebar,
SplitPanel: splitPanel,
ContentSplit: contentSplit,
BottomPanel: bottomPanel,
Explorer: explorer,
Search: search,
Changes: changes,
MenuBar: menuBar,
StatusBar: statusBar,
Status: status,
Borders: borders,
Settings: &cfg.Settings,
Workspace: ws,
Palette: BuildTerminalPalettePtr(cfg.Theme),
TerminalPanel: terminalPanel,
Problems: problems,
References: references,
DocVersions: make(map[string]int),
AllDiagnostics: make(map[string][]ui.Diagnostic),
LspNotified: make(map[string]bool),
Root: root,
EditorGroup: editorGroup,
Sidebar: sidebar,
SplitPanel: splitPanel,
ContentSplit: contentSplit,
BottomPanel: bottomPanel,
Explorer: explorer,
Search: search,
Changes: changes,
MenuBar: menuBar,
StatusBar: statusBar,
Status: status,
Borders: borders,
Settings: &cfg.Settings,
Workspace: ws,
Palette: BuildTerminalPalettePtr(cfg.Theme),
TerminalPanel: terminalPanel,
Problems: problems,
References: references,
DocVersions: make(map[string]int),
AllDiagnostics: make(map[string][]ui.Diagnostic),
LspNotified: make(map[string]bool),
}
}
27 changes: 13 additions & 14 deletions internal/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ func DefaultLSPSettings() LSPSettings {
}

type EditorSettings struct {
TabSize int `json:"tabSize"`
InsertSpaces bool `json:"insertSpaces"`
WordWrap bool `json:"wordWrap"`
LineNumbers bool `json:"lineNumbers"`
CursorStyle string `json:"cursorStyle,omitempty"`
FormatOnSave bool `json:"formatOnSave"`
InsertFinalNewline bool `json:"insertFinalNewline"`
TrimTrailingWhitespace bool `json:"trimTrailingWhitespace"`
FocusOnOpen bool `json:"focusOnOpen"`
TabSize int `json:"tabSize"`
InsertSpaces bool `json:"insertSpaces"`
WordWrap bool `json:"wordWrap"`
LineNumbers bool `json:"lineNumbers"`
CursorStyle string `json:"cursorStyle,omitempty"`
FormatOnSave bool `json:"formatOnSave"`
InsertFinalNewline bool `json:"insertFinalNewline"`
TrimTrailingWhitespace bool `json:"trimTrailingWhitespace"`
FocusOnOpen bool `json:"focusOnOpen"`
GitGutter *bool `json:"gitGutter,omitempty"`
GutterStyle string `json:"gutterStyle,omitempty"`
BracketPairColorization bool `json:"bracketPairColorization"`
Expand All @@ -95,10 +95,10 @@ func (e EditorSettings) IsGitGutterEnabled() bool {

func DefaultEditorSettings() EditorSettings {
return EditorSettings{
TabSize: 4,
InsertSpaces: true,
LineNumbers: true,
InsertFinalNewline: true,
TabSize: 4,
InsertSpaces: true,
LineNumbers: true,
InsertFinalNewline: true,
GutterStyle: "compact",
BracketPairColorization: false,
}
Expand Down Expand Up @@ -168,7 +168,6 @@ func LoadSettings() Settings {
return s
}


func SaveSettings(s Settings) error {
path := ConfigFilePath("settings.json")
data, err := json.MarshalIndent(s, "", " ")
Expand Down
1 change: 0 additions & 1 deletion internal/config/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ func TestDefaultLSPSettings(t *testing.T) {
}
}


func TestReferenceSettingsMatchesDefaults(t *testing.T) {
_, thisFile, _, _ := runtime.Caller(0)
refPath := filepath.Join(filepath.Dir(thisFile), "..", "..", "config", "settings.json")
Expand Down
18 changes: 13 additions & 5 deletions internal/config/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ type DiffStyles struct {
GutterAdded StyleDef `json:"gutterAdded,omitempty"`
GutterDeleted StyleDef `json:"gutterDeleted,omitempty"`
GutterModified StyleDef `json:"gutterModified,omitempty"`
CommentBg StyleDef `json:"commentBg,omitempty"`
CommentAuthor StyleDef `json:"commentAuthor,omitempty"`
}

type SyntaxStyles struct {
Expand Down Expand Up @@ -235,11 +237,11 @@ func DefaultTheme() ThemeConfig {
Border: StyleDef{Fg: "#555555"},

Editor: EditorStyles{
ActiveLine: StyleDef{Bg: "#282828"},
Selection: StyleDef{Bg: "#282828"},
LineNumber: StyleDef{Fg: "#999999"},
SearchMatch: StyleDef{Bg: "#623800"},
SearchActive: StyleDef{Bg: "#9e6a03"},
ActiveLine: StyleDef{Bg: "#282828"},
Selection: StyleDef{Bg: "#282828"},
LineNumber: StyleDef{Fg: "#999999"},
SearchMatch: StyleDef{Bg: "#623800"},
SearchActive: StyleDef{Bg: "#9e6a03"},
BracketMatch: StyleDef{Bg: "#3a3a3a"},
BracketColors: []string{"yellow", "magenta", "blue"},
},
Expand Down Expand Up @@ -288,6 +290,12 @@ func (t *ThemeConfig) ResolveColors() {
fillFg(&t.Diff.GutterAdded, "#73c991")
fillFg(&t.Diff.GutterDeleted, "#f14c4c")
fillFg(&t.Diff.GutterModified, "#e2c08d")
fillBg(&t.Diff.CommentBg, "#2d2d30")
fillFg(&t.Diff.CommentBg, t.Default.Fg)
fillFg(&t.Diff.CommentAuthor, "#569cd6")
if !t.Diff.CommentAuthor.Bold {
t.Diff.CommentAuthor.Bold = true
}
fillFg(&t.Success, "#73c991")
fillFg(&t.Danger, "#f14c4c")
fillFg(&t.Warning, "#e2c08d")
Expand Down
4 changes: 2 additions & 2 deletions internal/core/buffer/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ func DetectIndent(lines []string) IndentInfo {

// Buffer represents a text buffer with line-based storage.
type Buffer struct {
Lines []string
Dirty bool
Lines []string
Dirty bool
InsertFinalNewline bool
TrimTrailingWhitespace bool
LineEnding string // "\n" (LF) or "\r\n" (CRLF); defaults to "\n"
Expand Down
Loading
Loading