From cecb8f1f5bea3db23fc6683fd8e042b1f583c33f Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sun, 27 Jul 2025 00:18:53 +1000 Subject: [PATCH 01/15] chore: refactoring tdrl parsing out of main package --- main.go | 85 ++-------------------------------------- pkg/tdrl/tdrl_main.go | 90 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 82 deletions(-) create mode 100644 pkg/tdrl/tdrl_main.go diff --git a/main.go b/main.go index 4648170..402475a 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,6 @@ package main import ( - // "flag" - // "fmt" - // "io/fs" - // "log" - // "os" "flag" "fmt" "io/fs" @@ -17,12 +12,7 @@ import ( "github.com/Tehhs/tdr/pkg/comments" "github.com/Tehhs/tdr/pkg/util" - - // "path/filepath" - // "strings" "github.com/Tehhs/tdr/pkg/tdrl" - - antlr_v4 "github.com/antlr4-go/antlr/v4" ) type TodoBlock struct { @@ -30,79 +20,9 @@ type TodoBlock struct { FileName string } -type TodoListener struct { - tdrl.BasetdrlListener - OnFindTodo *func(tags []string, content string) -} - -func (s *TodoListener) EnterTodoRule(ctx *tdrl.TodoRuleContext) { - var amountOfTags int = 0 - if ctx.TagRule() != nil && ctx.TagRule().TagList().GetChildCount() != 0 { - amountOfTags = (ctx.TagRule().TagList().GetChildCount() + 1) / 2 - } - - tags := []string{} - for i := range amountOfTags { - tags = append(tags, ctx.TagRule().TagList().TAG_ID(i).GetText()) - - } - - var contentParts []string = []string{} - if ctx.MessageContent() != nil { - for partIndex := range ctx.MessageContent().GetChildCount() { - contentParts = append(contentParts, fmt.Sprint(ctx.MessageContent().GetChild(partIndex))) - } - - } - - if s.OnFindTodo != nil { - var f func(tags []string, content string) - f = *s.OnFindTodo - f(tags, strings.Join(contentParts, " ")) - } -} type ProcessedTag struct { } -type ProcessedTodo struct { - Tags []string - ProcessedContent string -} - -func (t ProcessedTodo) HasTag(checkTag string) bool { - for _, tag := range t.Tags { - if strings.ToLower(tag) == strings.ToLower(checkTag) { - return true - } - } - return false -} - -func ProcessTodo(content string) []ProcessedTodo { - charStream := antlr_v4.NewInputStream(content) - lexer := tdrl.NewtdrlLexer(charStream) - lexer.RemoveErrorListeners() - - tokens := antlr_v4.NewCommonTokenStream(lexer, antlr_v4.TokenDefaultChannel) - parser := tdrl.NewtdrlParser(tokens) - parser.RemoveErrorListeners() - - tree := parser.Main() - - processedTodos := []ProcessedTodo{} - - listener := &TodoListener{} - onFindTodo := func(tags []string, content string) { - processedTodos = append(processedTodos, ProcessedTodo{ - Tags: tags, - ProcessedContent: content, - }) - } - listener.OnFindTodo = &onFindTodo - antlr_v4.ParseTreeWalkerDefault.Walk(listener, tree) - - return processedTodos -} type ProcessArguments struct { List *bool @@ -162,8 +82,9 @@ func ProcessFile(path string) []TodoBlock { } -func main() { +func main() { + tdrlParser := tdrl.NewParser() args := ParseArguments() fileOrFolderInfo, err := os.Stat(*args.Path) @@ -200,7 +121,7 @@ func main() { todoContent = strings.ToLower(todoContent) - processedTodos := ProcessTodo(todoContent) + processedTodos := tdrlParser.ProcessTodo(todoContent) //i know this is a code smell and a half will refactor later :P if *args.List { diff --git a/pkg/tdrl/tdrl_main.go b/pkg/tdrl/tdrl_main.go new file mode 100644 index 0000000..ac8784b --- /dev/null +++ b/pkg/tdrl/tdrl_main.go @@ -0,0 +1,90 @@ +package tdrl + +import ( + "fmt" + "strings" + + antlr_v4 "github.com/antlr4-go/antlr/v4" +) + +type TDRLTag struct { +} +type TDRLTodo struct { + Tags []string + ProcessedContent string +} + +func (t TDRLTodo) HasTag(checkTag string) bool { + for _, tag := range t.Tags { + if strings.ToLower(tag) == strings.ToLower(checkTag) { + return true + } + } + return false +} + +type TDRListener struct { + BasetdrlListener + OnFindTodo *func(tags []string, content string) +} + +func (s *TDRListener) EnterTodoRule(ctx *TodoRuleContext) { + var amountOfTags int = 0 + if ctx.TagRule() != nil && ctx.TagRule().TagList().GetChildCount() != 0 { + amountOfTags = (ctx.TagRule().TagList().GetChildCount() + 1) / 2 + } + + tags := []string{} + for i := range amountOfTags { + tags = append(tags, ctx.TagRule().TagList().TAG_ID(i).GetText()) + + } + + var contentParts []string = []string{} + if ctx.MessageContent() != nil { + for partIndex := range ctx.MessageContent().GetChildCount() { + contentParts = append(contentParts, fmt.Sprint(ctx.MessageContent().GetChild(partIndex))) + } + + } + + if s.OnFindTodo != nil { + var f func(tags []string, content string) + f = *s.OnFindTodo + f(tags, strings.Join(contentParts, " ")) + } +} + +type TDRLanguageParser struct { + +} + +func NewParser() TDRLanguageParser { + return TDRLanguageParser{} +} + +func (p TDRLanguageParser) ProcessTodo(content string) []TDRLTodo { + charStream := antlr_v4.NewInputStream(content) + lexer := NewtdrlLexer(charStream) + lexer.RemoveErrorListeners() + + tokens := antlr_v4.NewCommonTokenStream(lexer, antlr_v4.TokenDefaultChannel) + parser := NewtdrlParser(tokens) + parser.RemoveErrorListeners() + + tree := parser.Main() + + processedTodos := []TDRLTodo{} + + listener := &TDRListener{} + onFindTodo := func(tags []string, content string) { + processedTodos = append(processedTodos, TDRLTodo{ + Tags: tags, + ProcessedContent: content, + }) + } + listener.OnFindTodo = &onFindTodo + antlr_v4.ParseTreeWalkerDefault.Walk(listener, tree) + + return processedTodos +} From 86e78dbf3c9c85756f3581001b60c2ca83607b9a Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sun, 27 Jul 2025 00:36:55 +1000 Subject: [PATCH 02/15] chore: refactor new core package stuff out of main and into core package --- main.go | 56 ++++---------------------------------------- pkg/core/core.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 51 deletions(-) create mode 100644 pkg/core/core.go diff --git a/main.go b/main.go index 402475a..e843d20 100644 --- a/main.go +++ b/main.go @@ -10,19 +10,11 @@ import ( "slices" "strings" - "github.com/Tehhs/tdr/pkg/comments" - "github.com/Tehhs/tdr/pkg/util" + "github.com/Tehhs/tdr/pkg/core" "github.com/Tehhs/tdr/pkg/tdrl" ) -type TodoBlock struct { - CommentBlock *comments.CommentBlock - FileName string -} - -type ProcessedTag struct { -} type ProcessArguments struct { List *bool @@ -42,48 +34,10 @@ func ParseArguments() ProcessArguments { return processArguments } -func ProcessFile(path string) []TodoBlock { - - var todoCommentBlocks []TodoBlock = []TodoBlock{} - - var extension *string = nil - filePathParts := strings.Split(path, ".") - extension = util.Ptr(filePathParts[len(filePathParts)-1]) - - content, err := os.ReadFile(path) - if err != nil { - log.Panicf("File '%s' could not be read.", path) - } - - //todo(refactor): Comments layer should return comment layer errors, and - //have a special file extension not supported error to check for here - //instead of just ingoring. - parseResult, err := comments.Parse(Ptr(string(content)), *extension) - - if err != nil { - // log.Panic("Error parsing") - } - - if parseResult == nil || parseResult.Comments == nil { - return todoCommentBlocks - } - - for _, commentBlock := range parseResult.Comments { - hasTodo := strings.Contains(strings.ToLower(commentBlock.String()), "todo") - if hasTodo { - todoCommentBlocks = append(todoCommentBlocks, TodoBlock{ - CommentBlock: commentBlock, - FileName: path, - }) - } - } - - return todoCommentBlocks - -} func main() { + coreInstance := core.NewTDRCore() tdrlParser := tdrl.NewParser() args := ParseArguments() @@ -92,10 +46,10 @@ func main() { log.Panicf("File or folder '%s' is not a file or folder.\n", *args.Path) } - var todoCommentBlocks []TodoBlock = []TodoBlock{} + var todoCommentBlocks []core.TodoBlock = []core.TodoBlock{} if !fileOrFolderInfo.IsDir() { - newBlocks := ProcessFile(*args.Path) + newBlocks := coreInstance.ProcessFile(*args.Path) todoCommentBlocks = append(todoCommentBlocks, newBlocks...) } else { filepath.Walk(*args.Path, func(path string, info fs.FileInfo, err error) error { @@ -103,7 +57,7 @@ func main() { if info.IsDir() { return nil } - newBlocks := ProcessFile(path) + newBlocks := coreInstance.ProcessFile(path) todoCommentBlocks = append(todoCommentBlocks, newBlocks...) return nil diff --git a/pkg/core/core.go b/pkg/core/core.go new file mode 100644 index 0000000..de83f9d --- /dev/null +++ b/pkg/core/core.go @@ -0,0 +1,61 @@ +package core + +import ( + "log" + "os" + "strings" + + "github.com/Tehhs/tdr/pkg/comments" + "github.com/Tehhs/tdr/pkg/util" +) + +type TDRCore struct { +} + +func NewTDRCore() TDRCore { + return TDRCore{} +} + +//todo(refactor): not sure if this should be here +type TodoBlock struct { + CommentBlock *comments.CommentBlock + FileName string +} + +func (c TDRCore) ProcessFile(path string) []TodoBlock { + var todoCommentBlocks []TodoBlock = []TodoBlock{} + + var extension *string = nil + filePathParts := strings.Split(path, ".") + extension = util.Ptr(filePathParts[len(filePathParts)-1]) + + content, err := os.ReadFile(path) + if err != nil { + log.Panicf("File '%s' could not be read.", path) + } + + //todo(refactor): Comments layer should return comment layer errors, and + //have a special file extension not supported error to check for here + //instead of just ingoring. + parseResult, err := comments.Parse(util.Ptr(string(content)), *extension) + + if err != nil { + // log.Panic("Error parsing") + } + + if parseResult == nil || parseResult.Comments == nil { + return todoCommentBlocks + } + + for _, commentBlock := range parseResult.Comments { + hasTodo := strings.Contains(strings.ToLower(commentBlock.String()), "todo") + if hasTodo { + todoCommentBlocks = append(todoCommentBlocks, TodoBlock{ + CommentBlock: commentBlock, + FileName: path, + }) + } + } + + return todoCommentBlocks +} From 77dda3010c8aec08a5bc43a94885dcaec794fc1f Mon Sep 17 00:00:00 2001 From: Tehhs Date: Thu, 31 Jul 2025 22:14:06 +1000 Subject: [PATCH 03/15] feat: js support --- go.mod | 2 + pkg/comments/golang_test.go | 6 +- pkg/comments/javascript.go | 129 ++++++++++++++++++++++++++++++++ pkg/comments/javascript_test.go | 100 +++++++++++++++++++++++++ pkg/comments/parser.go | 1 + 5 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 pkg/comments/javascript.go create mode 100644 pkg/comments/javascript_test.go diff --git a/go.mod b/go.mod index 9b8de54..00cc8f5 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ toolchain go1.24.3 require github.com/antlr4-go/antlr/v4 v4.13.1 +require github.com/tree-sitter/tree-sitter-javascript v0.23.1 // indirect + require ( github.com/mattn/go-pointer v0.0.1 // indirect github.com/tree-sitter/go-tree-sitter v0.25.0 diff --git a/pkg/comments/golang_test.go b/pkg/comments/golang_test.go index fe2d9a7..2df682f 100644 --- a/pkg/comments/golang_test.go +++ b/pkg/comments/golang_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestBasicComments(t *testing.T) { +func TestBasicGolangComments(t *testing.T) { var content string = ` package test @@ -35,7 +35,7 @@ func SomeFunction(str *string) { -func TestMultipleComments(t *testing.T) { +func TestMultipleGolangComments(t *testing.T) { var content string = ` package test @@ -65,7 +65,7 @@ func SomeFunction(str *string) { } -func TestSequentialLineComments(t *testing.T) { +func TestSequentialGolangLineComments(t *testing.T) { var content string = ` package test diff --git a/pkg/comments/javascript.go b/pkg/comments/javascript.go new file mode 100644 index 0000000..e9f5677 --- /dev/null +++ b/pkg/comments/javascript.go @@ -0,0 +1,129 @@ +package comments + +import ( + "errors" + "fmt" + "strings" + + tree_sitter "github.com/tree-sitter/go-tree-sitter" + tree_sitter_js "github.com/tree-sitter/tree-sitter-javascript/bindings/go" +) + + +type JavascriptParser struct { + Parser +} + +func (p JavascriptParser) Parse(content *string) (*ParseResult, error) { + if content == nil { + return nil, errors.New("content is nil") + } + parser := tree_sitter.NewParser() + defer parser.Close() + + parser.SetLanguage(tree_sitter.NewLanguage(tree_sitter_js.Language())) + tree := parser.Parse([]byte(*content), nil) + defer tree.Close() + + if tree == nil { + fmt.Println("Failed to parse the code") + return nil, nil + } + + cursor := tree.RootNode().Walk() + defer cursor.Close() + + parseResult := ParseResult{} + + var commentBlock *CommentBlock = nil + var lastLine int = -2 + var evalTodo func(content string, line int) = func(content string, line int) { + + if line == lastLine+1 { + commentBlock.Lines = append(commentBlock.Lines, Line{ + Content: content, + LineNumber: line+1, + }) + commentBlock.EndLine = line+1 + lastLine = line + return + } + + commentBlock = &CommentBlock{} + parseResult.Comments = append(parseResult.Comments, commentBlock) + commentBlock.StartLine = line+1 + commentBlock.EndLine = line+1 + commentBlock.Lines = append(commentBlock.Lines, Line{ + Content: content, + LineNumber: line+1, + }) + lastLine = line + + } + + var evalComment func(*tree_sitter.Node) = func(node *tree_sitter.Node) { + + stringContent := string(node.Utf8Text([]byte(*content))) + stringContent = strings.TrimSpace(stringContent) + + if stringContent[0:2] == "//" { + stringContent = stringContent[2:] + } else { + stringContent = stringContent[2:] + stringContent = stringContent[:len(stringContent)-2] + } + // fmt.Println(node.) + // fmt.Println(string(node.Utf8Text(code))) + // fmt.Println(node.EndPosition().Row) + + line := node.EndPosition().Row + + evalTodo(stringContent, int(line)) + + } + + var evalNode func(*tree_sitter.Node) = func(node *tree_sitter.Node) { + if node.GrammarName() != "comment" { + return + } + evalComment(node) + } + + for { + + //eval element here + evalNode(cursor.Node()) + + if cursor.GotoFirstChild() { + continue + } + + if cursor.GotoNextSibling() { + continue + } + + //Infinitly go backwards and try to find next ancestor sibling + var shouldBreak bool = false + for { + if cursor.GotoParent() { + if cursor.GotoNextSibling() { + break + } + } else { + shouldBreak = true + break + } + } + if shouldBreak { + break + } + + } + + return &parseResult, nil + +} + +func (p JavascriptParser) ShouldParseFile(extension string) bool { + return extension == "js" +} diff --git a/pkg/comments/javascript_test.go b/pkg/comments/javascript_test.go new file mode 100644 index 0000000..b2b2f56 --- /dev/null +++ b/pkg/comments/javascript_test.go @@ -0,0 +1,100 @@ +package comments + +import ( + "strings" + "testing" +) + +func TestBasicJavascriptComments(t *testing.T) { + + var content string = ` + + + ;(()=>{ + const a = "test" + //todo(test): this comment should be picked up + })(); + + + ` + + parseResult, err := Parse(&content, "js") + + if err != nil { + t.Errorf("Failed to parse %v", err) + } + + if parseResult == nil { + t.Errorf("Parse result returned nil") + } + + if len(parseResult.Comments) != 1 { + t.Errorf("Did not parse the correct amount of comments; Got %d", len(parseResult.Comments)) + } + +} + + + +func TestMultipleJavascriptComments(t *testing.T) { + + var content string = ` + +function f(){ + //todo: this is another comment that should be picked up + const number = 2 + //todo(test): this comment should be picked up +} + + ` + + parseResult, err := Parse(&content, "go") + + if err != nil { + t.Errorf("Failed to parse %v", err) + } + + if parseResult == nil { + t.Errorf("Parse result returned nil") + } + + if len(parseResult.Comments) != 2 { + t.Errorf("Did not parse the correct amount of comments; Got %d", len(parseResult.Comments)) + } + +} + + +func TestSequentialLineJavascriptComments(t *testing.T) { + + var content string = ` + +function SomeFunction() { + const number = 2 + //todo(test): this comment should be picked up + //and this should also be in the same comment block TESTFORTHISSTRING +} + + ` + + parseResult, err := Parse(&content, "go") + + if err != nil { + t.Errorf("Failed to parse %v", err) + } + + if parseResult == nil { + t.Errorf("Parse result returned nil") + } + + if len(parseResult.Comments) == 0 { + t.Errorf("Did not pick up on any errors") + } + + commentBlockStr := parseResult.Comments[0].String() + if !strings.Contains(commentBlockStr, "TESTFORTHISSTRING") { + t.Error("Did not pick up on second line of comment string") + } + +} + diff --git a/pkg/comments/parser.go b/pkg/comments/parser.go index 334ef12..c1b516d 100644 --- a/pkg/comments/parser.go +++ b/pkg/comments/parser.go @@ -39,6 +39,7 @@ type Parser interface { var parsers []Parser = []Parser{ GoParser{}, + JavascriptParser{}, } func Parse(content *string, extension string) (*ParseResult, error) { From 470bc8e69c008760a004cce71190d8480a8397ce Mon Sep 17 00:00:00 2001 From: Tehhs Date: Thu, 31 Jul 2025 22:14:42 +1000 Subject: [PATCH 04/15] chore: add todo comment --- pkg/comments/javascript_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/comments/javascript_test.go b/pkg/comments/javascript_test.go index b2b2f56..bc0db33 100644 --- a/pkg/comments/javascript_test.go +++ b/pkg/comments/javascript_test.go @@ -98,3 +98,5 @@ function SomeFunction() { } +//todo(testing): add jsx tests + From d8878d62bd1d9ab5c89a00b0dd07def8194b648c Mon Sep 17 00:00:00 2001 From: Tehhs Date: Thu, 31 Jul 2025 22:19:25 +1000 Subject: [PATCH 05/15] chore: add todo for later refactoring --- pkg/comments/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/comments/parser.go b/pkg/comments/parser.go index c1b516d..2a12381 100644 --- a/pkg/comments/parser.go +++ b/pkg/comments/parser.go @@ -4,7 +4,7 @@ import ( "errors" "log" ) - +//todo(refactor): most tree sitter parsers are all the same you should make a util func for that instead of copy pasta type Line struct { Content string LineNumber int From 8474bb4bb4132ecc43c2de056bc1f913c061bca2 Mon Sep 17 00:00:00 2001 From: Liam Tormey Date: Fri, 1 Aug 2025 09:54:13 +1000 Subject: [PATCH 06/15] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9159d1..4e8597a 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,9 @@ And then query your tags by doing something like `tdr -t security` or `tdr -t im # Supporpted Lanauges * Golang -* (WIP) Javascript & JSX +* Javascript (including JSX) * (WIP) Typescript & TSX +* (WIP) C# # Building From 084bc956a7f52f12f5ed32e115401304d521023a Mon Sep 17 00:00:00 2001 From: Tehhs Date: Tue, 23 Sep 2025 14:46:15 +1000 Subject: [PATCH 07/15] feat: generic tree sitter parsers --- pkg/comments/golang.go | 131 ------------------ pkg/comments/parser.go | 21 ++- .../{javascript.go => tree_sitter_parser.go} | 52 +++++-- 3 files changed, 59 insertions(+), 145 deletions(-) delete mode 100644 pkg/comments/golang.go rename pkg/comments/{javascript.go => tree_sitter_parser.go} (69%) diff --git a/pkg/comments/golang.go b/pkg/comments/golang.go deleted file mode 100644 index ed05617..0000000 --- a/pkg/comments/golang.go +++ /dev/null @@ -1,131 +0,0 @@ -package comments - -import ( - "errors" - "fmt" - "strings" - - tree_sitter "github.com/tree-sitter/go-tree-sitter" - tree_sitter_go "github.com/tree-sitter/tree-sitter-go/bindings/go" -) - -type GoParser struct { - Parser -} - -func (p GoParser) Parse(content *string) (*ParseResult, error) { - if content == nil { - return nil, errors.New("content is nil") - } - parser := tree_sitter.NewParser() - defer parser.Close() - - parser.SetLanguage(tree_sitter.NewLanguage(tree_sitter_go.Language())) - tree := parser.Parse([]byte(*content), nil) - defer tree.Close() - - if tree == nil { - fmt.Println("Failed to parse the code") - return nil, nil - } - - cursor := tree.RootNode().Walk() - defer cursor.Close() - - parseResult := ParseResult{} - - var commentBlock *CommentBlock = nil - var lastLine int = -2 - var evalTodo func(content string, line int) = func(content string, line int) { - - if line == lastLine+1 { - commentBlock.Lines = append(commentBlock.Lines, Line{ - Content: content, - LineNumber: line+1, - }) - commentBlock.EndLine = line+1 - lastLine = line - return - } - - commentBlock = &CommentBlock{} - parseResult.Comments = append(parseResult.Comments, commentBlock) - commentBlock.StartLine = line+1 - commentBlock.EndLine = line+1 - commentBlock.Lines = append(commentBlock.Lines, Line{ - Content: content, - LineNumber: line+1, - }) - lastLine = line - - } - - var evalComment func(*tree_sitter.Node) = func(node *tree_sitter.Node) { - - stringContent := string(node.Utf8Text([]byte(*content))) - stringContent = strings.TrimSpace(stringContent) - - if stringContent[0:2] == "//" { - stringContent = stringContent[2:] - } else { - stringContent = stringContent[2:] - stringContent = stringContent[:len(stringContent)-2] - } - // fmt.Println(node.) - // fmt.Println(string(node.Utf8Text(code))) - // fmt.Println(node.EndPosition().Row) - - line := node.EndPosition().Row - - evalTodo(stringContent, int(line)) - - } - - var evalNode func(*tree_sitter.Node) = func(node *tree_sitter.Node) { - if node.GrammarName() != "comment" { - return - } - evalComment(node) - } - - for { - - //eval element here - evalNode(cursor.Node()) - - if cursor.GotoFirstChild() { - continue - } - - if cursor.GotoNextSibling() { - continue - } - - //Infinitly go backwards and try to find next ancestor sibling - var shouldBreak bool = false - for { - if cursor.GotoParent() { - if cursor.GotoNextSibling() { - break - } - } else { - shouldBreak = true - break - } - } - if shouldBreak { - break - } - - } - - return &parseResult, nil - -} - -func (p GoParser) ShouldParseFile(extension string) bool { - if extension[len(extension)-2:] == "go" { - return true - } - return false -} diff --git a/pkg/comments/parser.go b/pkg/comments/parser.go index 2a12381..cf21f3b 100644 --- a/pkg/comments/parser.go +++ b/pkg/comments/parser.go @@ -3,7 +3,12 @@ package comments import ( "errors" "log" + + tree_sitter "github.com/tree-sitter/go-tree-sitter" + tree_sitter_go "github.com/tree-sitter/tree-sitter-go/bindings/go" + tree_sitter_js "github.com/tree-sitter/tree-sitter-javascript/bindings/go" ) + //todo(refactor): most tree sitter parsers are all the same you should make a util func for that instead of copy pasta type Line struct { Content string @@ -37,9 +42,19 @@ type Parser interface { ShouldParseFile(extension string) bool } +func ParserFromTreeSitter() { + +} + var parsers []Parser = []Parser{ - GoParser{}, - JavascriptParser{}, + TreeSitterParser{ + Language: tree_sitter.NewLanguage(tree_sitter_go.Language()), + IsLanguageFile: Extensions("go"), + }, + TreeSitterParser{ + Language: tree_sitter.NewLanguage(tree_sitter_js.Language()), + IsLanguageFile: Extensions("js"), + }, } func Parse(content *string, extension string) (*ParseResult, error) { @@ -55,3 +70,5 @@ func Parse(content *string, extension string) (*ParseResult, error) { } return nil, errors.New("no supported parser found") } + + diff --git a/pkg/comments/javascript.go b/pkg/comments/tree_sitter_parser.go similarity index 69% rename from pkg/comments/javascript.go rename to pkg/comments/tree_sitter_parser.go index e9f5677..33c48bc 100644 --- a/pkg/comments/javascript.go +++ b/pkg/comments/tree_sitter_parser.go @@ -3,25 +3,49 @@ package comments import ( "errors" "fmt" + "log" "strings" tree_sitter "github.com/tree-sitter/go-tree-sitter" - tree_sitter_js "github.com/tree-sitter/tree-sitter-javascript/bindings/go" ) +type IsLanguageFileFunction func(ext string) bool -type JavascriptParser struct { +type TreeSitterParser struct { Parser + Language *tree_sitter.Language + IsLanguageFile IsLanguageFileFunction } -func (p JavascriptParser) Parse(content *string) (*ParseResult, error) { +func Extensions(exts... string) IsLanguageFileFunction { + return func(ext string) bool { + ext = strings.ToLower(ext) + ext = strings.TrimSpace(ext) + for _, e := range exts { + e = strings.ToLower(e) + e = strings.TrimSpace(e) + if e == ext { + return true + } + } + return false + } +} + +func (p TreeSitterParser) Parse(content *string) (*ParseResult, error) { + + if p.Language == nil { + log.Panic("no language set up for this tree sitter parser") + } + + if content == nil { return nil, errors.New("content is nil") } parser := tree_sitter.NewParser() defer parser.Close() - parser.SetLanguage(tree_sitter.NewLanguage(tree_sitter_js.Language())) + parser.SetLanguage(p.Language) tree := parser.Parse([]byte(*content), nil) defer tree.Close() @@ -42,20 +66,20 @@ func (p JavascriptParser) Parse(content *string) (*ParseResult, error) { if line == lastLine+1 { commentBlock.Lines = append(commentBlock.Lines, Line{ Content: content, - LineNumber: line+1, + LineNumber: line + 1, }) - commentBlock.EndLine = line+1 + commentBlock.EndLine = line + 1 lastLine = line return } commentBlock = &CommentBlock{} parseResult.Comments = append(parseResult.Comments, commentBlock) - commentBlock.StartLine = line+1 - commentBlock.EndLine = line+1 + commentBlock.StartLine = line + 1 + commentBlock.EndLine = line + 1 commentBlock.Lines = append(commentBlock.Lines, Line{ Content: content, - LineNumber: line+1, + LineNumber: line + 1, }) lastLine = line @@ -124,6 +148,10 @@ func (p JavascriptParser) Parse(content *string) (*ParseResult, error) { } -func (p JavascriptParser) ShouldParseFile(extension string) bool { - return extension == "js" -} + +func (p TreeSitterParser) ShouldParseFile(extension string) bool { + if p.IsLanguageFile == nil { + return false + } + return p.IsLanguageFile(extension) +} \ No newline at end of file From f31dc6c75715afdef57d248778c3f4b66548508d Mon Sep 17 00:00:00 2001 From: Tehhs Date: Tue, 23 Sep 2025 15:03:37 +1000 Subject: [PATCH 08/15] feat: new languages support for ts, csharap. working on bash and tsx support. still need to add tests --- go.mod | 7 ++++++- go.sum | 6 ++++++ pkg/comments/parser.go | 24 ++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 00cc8f5..947a597 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,12 @@ toolchain go1.24.3 require github.com/antlr4-go/antlr/v4 v4.13.1 -require github.com/tree-sitter/tree-sitter-javascript v0.23.1 // indirect +require ( + github.com/tree-sitter/tree-sitter-bash v0.25.0 // indirect + github.com/tree-sitter/tree-sitter-c-sharp v0.23.1 // indirect + github.com/tree-sitter/tree-sitter-javascript v0.23.1 // indirect + github.com/tree-sitter/tree-sitter-typescript v0.23.2 // indirect +) require ( github.com/mattn/go-pointer v0.0.1 // indirect diff --git a/go.sum b/go.sum index 3d89ff2..19fd25e 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tree-sitter/go-tree-sitter v0.25.0 h1:sx6kcg8raRFCvc9BnXglke6axya12krCJF5xJ2sftRU= github.com/tree-sitter/go-tree-sitter v0.25.0/go.mod h1:r77ig7BikoZhHrrsjAnv8RqGti5rtSyvDHPzgTPsUuU= +github.com/tree-sitter/tree-sitter-bash v0.25.0 h1:ghfv/gVgi2wF6Yv5hTpvjKm5H25/6J9IhNvgi0iYhcs= +github.com/tree-sitter/tree-sitter-bash v0.25.0/go.mod h1:AksQ6zE+sP9hnp7mKTMT7Q+CwpthV7VGQLXvweVXz9U= github.com/tree-sitter/tree-sitter-c v0.23.4 h1:nBPH3FV07DzAD7p0GfNvXM+Y7pNIoPenQWBpvM++t4c= github.com/tree-sitter/tree-sitter-c v0.23.4/go.mod h1:MkI5dOiIpeN94LNjeCp8ljXN/953JCwAby4bClMr6bw= +github.com/tree-sitter/tree-sitter-c-sharp v0.23.1 h1:ddG6osP34sMieVNN6lu5ZG/3N8Wn+67+43BmipqidyM= +github.com/tree-sitter/tree-sitter-c-sharp v0.23.1/go.mod h1:H7/aFm5vR1A8Yn5VIOfLWPdlKuJsMgZ5eDmaJdv8bY0= github.com/tree-sitter/tree-sitter-cpp v0.23.4 h1:LaWZsiqQKvR65yHgKmnaqA+uz6tlDJTJFCyFIeZU/8w= github.com/tree-sitter/tree-sitter-cpp v0.23.4/go.mod h1:doqNW64BriC7WBCQ1klf0KmJpdEvfxyXtoEybnBo6v8= github.com/tree-sitter/tree-sitter-embedded-template v0.23.2 h1:nFkkH6Sbe56EXLmZBqHHcamTpmz3TId97I16EnGy4rg= @@ -34,6 +38,8 @@ github.com/tree-sitter/tree-sitter-ruby v0.23.1 h1:T/NKHUA+iVbHM440hFx+lzVOzS4dV github.com/tree-sitter/tree-sitter-ruby v0.23.1/go.mod h1:kUS4kCCQloFcdX6sdpr8p6r2rogbM6ZjTox5ZOQy8cA= github.com/tree-sitter/tree-sitter-rust v0.23.2 h1:6AtoooCW5GqNrRpfnvl0iUhxTAZEovEmLKDbyHlfw90= github.com/tree-sitter/tree-sitter-rust v0.23.2/go.mod h1:hfeGWic9BAfgTrc7Xf6FaOAguCFJRo3RBbs7QJ6D7MI= +github.com/tree-sitter/tree-sitter-typescript v0.23.2 h1:/Odvphn18PniVixb9e97X0DbNVsU6Qocv9mfkyzdXwU= +github.com/tree-sitter/tree-sitter-typescript v0.23.2/go.mod h1:zjzMXT/Ulffel2xfOcAkQQkiAkmgnbtPGlFQw/5X4xA= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/comments/parser.go b/pkg/comments/parser.go index cf21f3b..5104992 100644 --- a/pkg/comments/parser.go +++ b/pkg/comments/parser.go @@ -7,6 +7,9 @@ import ( tree_sitter "github.com/tree-sitter/go-tree-sitter" tree_sitter_go "github.com/tree-sitter/tree-sitter-go/bindings/go" tree_sitter_js "github.com/tree-sitter/tree-sitter-javascript/bindings/go" + tree_sitter_ts "github.com/tree-sitter/tree-sitter-typescript/bindings/go" + tree_sitter_csharp "github.com/tree-sitter/tree-sitter-c-sharp/bindings/go" + tree_sitter_bash "github.com/tree-sitter/tree-sitter-bash/bindings/go" ) //todo(refactor): most tree sitter parsers are all the same you should make a util func for that instead of copy pasta @@ -55,6 +58,27 @@ var parsers []Parser = []Parser{ Language: tree_sitter.NewLanguage(tree_sitter_js.Language()), IsLanguageFile: Extensions("js"), }, + TreeSitterParser{ + Language: tree_sitter.NewLanguage(tree_sitter_ts.LanguageTypescript()), + IsLanguageFile: Extensions("ts"), + }, + TreeSitterParser{ + Language: tree_sitter.NewLanguage(tree_sitter_csharp.Language()), + IsLanguageFile: Extensions("cs"), + }, + TreeSitterParser{ + Language: tree_sitter.NewLanguage(tree_sitter_bash.Language()), + IsLanguageFile: Extensions("sh"), + //Not sure about this. Like comment below, might need to control comment stripping + //As bash requires stripping the # + }, + // TSX (and jsx) will invole controlling how comments are parsed more than just stripping out the + // beginning '//' + // + // TreeSitterParser{ + // Language: tree_sitter.NewLanguage(tree_sitter_ts.LanguageTypescript()), + // IsLanguageFile: Extensions("tsx"), + // }, } func Parse(content *string, extension string) (*ParseResult, error) { From dae20ea00c618c7660fa9d06f783600e3533a4b5 Mon Sep 17 00:00:00 2001 From: Tehhs Date: Tue, 23 Sep 2025 15:27:38 +1000 Subject: [PATCH 09/15] feat!!: generic comment strippers each language can choose how to strip out comments before parsing now. One test failing on golang need to fix later. --- pkg/comments/parser.go | 7 ++- pkg/comments/tree_sitter_parser.go | 70 ++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/pkg/comments/parser.go b/pkg/comments/parser.go index 5104992..9ec3529 100644 --- a/pkg/comments/parser.go +++ b/pkg/comments/parser.go @@ -53,24 +53,27 @@ var parsers []Parser = []Parser{ TreeSitterParser{ Language: tree_sitter.NewLanguage(tree_sitter_go.Language()), IsLanguageFile: Extensions("go"), + Strip: StripPrescedingSlashes(), //todo: account for multiline }, TreeSitterParser{ Language: tree_sitter.NewLanguage(tree_sitter_js.Language()), IsLanguageFile: Extensions("js"), + Strip: StripPrescedingSlashes(), //todo: account for multiline }, TreeSitterParser{ Language: tree_sitter.NewLanguage(tree_sitter_ts.LanguageTypescript()), IsLanguageFile: Extensions("ts"), + Strip: StripPrescedingSlashes(), //todo: account for multiline }, TreeSitterParser{ Language: tree_sitter.NewLanguage(tree_sitter_csharp.Language()), IsLanguageFile: Extensions("cs"), + Strip: StripPrescedingSlashes(), //todo: account for multiline }, TreeSitterParser{ Language: tree_sitter.NewLanguage(tree_sitter_bash.Language()), IsLanguageFile: Extensions("sh"), - //Not sure about this. Like comment below, might need to control comment stripping - //As bash requires stripping the # + Strip: StripPrescedingCharacters("#"), }, // TSX (and jsx) will invole controlling how comments are parsed more than just stripping out the // beginning '//' diff --git a/pkg/comments/tree_sitter_parser.go b/pkg/comments/tree_sitter_parser.go index 33c48bc..a060b1d 100644 --- a/pkg/comments/tree_sitter_parser.go +++ b/pkg/comments/tree_sitter_parser.go @@ -10,34 +10,69 @@ import ( ) type IsLanguageFileFunction func(ext string) bool +type CommentStripperFunction func(comment string) string type TreeSitterParser struct { Parser - Language *tree_sitter.Language + Language *tree_sitter.Language IsLanguageFile IsLanguageFileFunction + Strip CommentStripperFunction } -func Extensions(exts... string) IsLanguageFileFunction { - return func(ext string) bool { +func Extensions(exts ...string) IsLanguageFileFunction { + return func(ext string) bool { ext = strings.ToLower(ext) ext = strings.TrimSpace(ext) - for _, e := range exts { + for _, e := range exts { e = strings.ToLower(e) e = strings.TrimSpace(e) - if e == ext { - return true + if e == ext { + return true } } return false } } +func StripPrescedingSlashes() CommentStripperFunction { + return StripPrescedingCharacters("//") + // return func(comment string) string { + // comment = strings.TrimSpace(comment) + // if comment[0:2] == "//" { + // comment = comment[2:] + // } else { + // comment = comment[2:] + // comment = comment[:len(comment)-2] + // } + // return comment + // } +} + +func StripPrescedingCharacters(prescedingCharacters string) CommentStripperFunction { + return func(comment string) string { + comment = strings.TrimSpace(comment) + if comment[0:len(prescedingCharacters)] == prescedingCharacters { + comment = comment[len(prescedingCharacters):] + } else { + comment = comment[len(prescedingCharacters):] + comment = comment[:len(comment)-len(prescedingCharacters)] + } + return comment + } +} + func (p TreeSitterParser) Parse(content *string) (*ParseResult, error) { - if p.Language == nil { + if p.Language == nil { log.Panic("no language set up for this tree sitter parser") } - + var stripFunction CommentStripperFunction = nil + if p.Strip != nil { + stripFunction = p.Strip + } else { + // stripFunction = StripPrescedingSlashes + panic("does not have strip function") + } if content == nil { return nil, errors.New("content is nil") @@ -90,15 +125,7 @@ func (p TreeSitterParser) Parse(content *string) (*ParseResult, error) { stringContent := string(node.Utf8Text([]byte(*content))) stringContent = strings.TrimSpace(stringContent) - if stringContent[0:2] == "//" { - stringContent = stringContent[2:] - } else { - stringContent = stringContent[2:] - stringContent = stringContent[:len(stringContent)-2] - } - // fmt.Println(node.) - // fmt.Println(string(node.Utf8Text(code))) - // fmt.Println(node.EndPosition().Row) + stringContent = stripFunction(stringContent) line := node.EndPosition().Row @@ -148,10 +175,9 @@ func (p TreeSitterParser) Parse(content *string) (*ParseResult, error) { } - -func (p TreeSitterParser) ShouldParseFile(extension string) bool { - if p.IsLanguageFile == nil { - return false +func (p TreeSitterParser) ShouldParseFile(extension string) bool { + if p.IsLanguageFile == nil { + return false } return p.IsLanguageFile(extension) -} \ No newline at end of file +} From d11336d7b8e1969dca278fc45340aff02d2414af Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sat, 4 Oct 2025 12:43:41 +1000 Subject: [PATCH 10/15] feat!: refactor more logic into core --- .vscode/launch.json | 2 +- cmd/dev/main.go | 25 +++++++++ go.mod | 23 +++++++- go.sum | 41 ++++++++++++++ main.go | 118 +-------------------------------------- pkg/cli/cli.go | 6 ++ pkg/core/core.go | 133 ++++++++++++++++++++++++++++++++++++++------ 7 files changed, 212 insertions(+), 136 deletions(-) create mode 100644 cmd/dev/main.go create mode 100644 pkg/cli/cli.go diff --git a/.vscode/launch.json b/.vscode/launch.json index d3927eb..4ffd92c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "go", "request": "launch", "mode": "debug", - "program": "${workspaceFolder}", + "program": "${workspaceFolder}/cmd/dev", // "console": "integratedTerminal", // "args": ["-f", "main.go", "-l"] }, diff --git a/cmd/dev/main.go b/cmd/dev/main.go new file mode 100644 index 0000000..65d7330 --- /dev/null +++ b/cmd/dev/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + + "github.com/Tehhs/tdr/pkg/core" +) + +func main() { + //todo(code): should pick up on this + coreInstance := core.NewTDRCore() + processOutput, err := coreInstance.Process("./main.go") + + if err != nil { + panic(err) + } + + for _, smth := range processOutput.Todos { + log.Printf("file %s", *smth.Name) + + for _, t := range smth.Processed { + log.Print(t) + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 947a597..0a7db78 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,35 @@ module github.com/Tehhs/tdr -go 1.23 +go 1.24.0 -toolchain go1.24.3 +toolchain go1.24.6 require github.com/antlr4-go/antlr/v4 v4.13.1 require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/tree-sitter/tree-sitter-bash v0.25.0 // indirect github.com/tree-sitter/tree-sitter-c-sharp v0.23.1 // indirect github.com/tree-sitter/tree-sitter-javascript v0.23.1 // indirect github.com/tree-sitter/tree-sitter-typescript v0.23.2 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.3.8 // indirect ) require ( diff --git a/go.sum b/go.sum index 19fd25e..3fb171c 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,44 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tree-sitter/go-tree-sitter v0.25.0 h1:sx6kcg8raRFCvc9BnXglke6axya12krCJF5xJ2sftRU= @@ -40,7 +73,15 @@ github.com/tree-sitter/tree-sitter-rust v0.23.2 h1:6AtoooCW5GqNrRpfnvl0iUhxTAZEo github.com/tree-sitter/tree-sitter-rust v0.23.2/go.mod h1:hfeGWic9BAfgTrc7Xf6FaOAguCFJRo3RBbs7QJ6D7MI= github.com/tree-sitter/tree-sitter-typescript v0.23.2 h1:/Odvphn18PniVixb9e97X0DbNVsU6Qocv9mfkyzdXwU= github.com/tree-sitter/tree-sitter-typescript v0.23.2/go.mod h1:zjzMXT/Ulffel2xfOcAkQQkiAkmgnbtPGlFQw/5X4xA= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index e843d20..5de3722 100644 --- a/main.go +++ b/main.go @@ -1,123 +1,11 @@ package main import ( - "flag" - "fmt" - "io/fs" - "log" - "os" - "path/filepath" - "slices" - "strings" - - "github.com/Tehhs/tdr/pkg/core" - "github.com/Tehhs/tdr/pkg/tdrl" + // "github.com/Tehhs/tdr/pkg/core" ) - -type ProcessArguments struct { - List *bool - TagFilter *string - Path *string -} - -func ParseArguments() ProcessArguments { - var processArguments ProcessArguments = ProcessArguments{ - List: flag.Bool("l", false, "List all tags"), - TagFilter: flag.String("t", "", "Tag"), - Path: flag.String("f", ".", "File or folder"), - } - - flag.Parse() - - return processArguments -} - - - func main() { - coreInstance := core.NewTDRCore() - tdrlParser := tdrl.NewParser() - args := ParseArguments() - - fileOrFolderInfo, err := os.Stat(*args.Path) - if err != nil { - log.Panicf("File or folder '%s' is not a file or folder.\n", *args.Path) - } - - var todoCommentBlocks []core.TodoBlock = []core.TodoBlock{} - - if !fileOrFolderInfo.IsDir() { - newBlocks := coreInstance.ProcessFile(*args.Path) - todoCommentBlocks = append(todoCommentBlocks, newBlocks...) - } else { - filepath.Walk(*args.Path, func(path string, info fs.FileInfo, err error) error { - - if info.IsDir() { - return nil - } - newBlocks := coreInstance.ProcessFile(path) - todoCommentBlocks = append(todoCommentBlocks, newBlocks...) - - return nil - }) - } - - listedTags := []string{} - for _, todoBlock := range todoCommentBlocks { - - todoLines := []string{} - for _, line := range todoBlock.CommentBlock.Lines { - todoLines = append(todoLines, line.String()) - } - todoContent := strings.Join(todoLines, " ") - - todoContent = strings.ToLower(todoContent) - - processedTodos := tdrlParser.ProcessTodo(todoContent) - - //i know this is a code smell and a half will refactor later :P - if *args.List { - for _, todo := range processedTodos { - for _, tag := range todo.Tags { - if slices.Contains(listedTags, strings.TrimSpace(tag)) { - continue - } - listedTags = append(listedTags, strings.TrimSpace(tag)) - } - } - } else if len(processedTodos) != 0 { - for _, tag := range processedTodos[0].Tags { - if strings.Contains(strings.ToLower(tag), strings.ToLower(*args.TagFilter)) { - fmt.Printf("\n%s(lines %d to %d):\n", todoBlock.FileName, todoBlock.CommentBlock.StartLine, todoBlock.CommentBlock.EndLine) - fmt.Printf("\t%v -> %s", processedTodos[0].Tags, processedTodos[0].ProcessedContent) - break - } - } - - } - - //todo(very_important): test this thing - - //todo(important): another test - - //todo(easy,ez): this should be easy and - //when this breaks into multiple lines - //it should handle that - } - - if *args.List { - fmt.Print("List of all todo tags:\n\n") - for _, tag := range listedTags { - fmt.Println(tag) - } - } - - fmt.Print("\n") - -} + -func Ptr[T any](v T) *T { - return &v -} +} \ No newline at end of file diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go new file mode 100644 index 0000000..42ed377 --- /dev/null +++ b/pkg/cli/cli.go @@ -0,0 +1,6 @@ +package cli + + +type TdrCli struct { + +} \ No newline at end of file diff --git a/pkg/core/core.go b/pkg/core/core.go index de83f9d..9bb137f 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -1,11 +1,13 @@ package core import ( + "fmt" "log" "os" "strings" "github.com/Tehhs/tdr/pkg/comments" + "github.com/Tehhs/tdr/pkg/tdrl" "github.com/Tehhs/tdr/pkg/util" ) @@ -16,46 +18,141 @@ func NewTDRCore() TDRCore { return TDRCore{} } -//todo(refactor): not sure if this should be here +// todo(refactor): not sure if this should be here type TodoBlock struct { CommentBlock *comments.CommentBlock - FileName string + RawContent *ContentType } -func (c TDRCore) ProcessFile(path string) []TodoBlock { - var todoCommentBlocks []TodoBlock = []TodoBlock{} +type ContentType struct { - var extension *string = nil - filePathParts := strings.Split(path, ".") - extension = util.Ptr(filePathParts[len(filePathParts)-1]) + //The actual content to process + Content *[]byte - content, err := os.ReadFile(path) - if err != nil { - log.Panicf("File '%s' could not be read.", path) + //Name of the content. This might be the filename, filepath+filename, + //or just any random name for the content in the case where there are + //no files + ContentName *string + + //In the case of files, this might be the extension name (example: "go", "js", "jsx"). + //This is required to work out how to extract comments from the language. If there is + //no file, you have to work out what the extension is and supply it here. + ContentType *string +} + +func (c TDRCore) getTodosFromContent(content ContentType) (*[]TodoBlock, error) { + if content.Content == nil { + return nil, fmt.Errorf("cannot process todo content with no actual content") + } + + if content.ContentType == nil { + return nil, fmt.Errorf("cannot process todo content with no content type") + } + + if content.ContentName == nil { + return nil, fmt.Errorf("cannot process todo content with no content name") } //todo(refactor): Comments layer should return comment layer errors, and //have a special file extension not supported error to check for here //instead of just ingoring. - parseResult, err := comments.Parse(util.Ptr(string(content)), *extension) + parseResult, err := comments.Parse(util.Ptr(string(*content.Content)), *content.ContentType) - if err != nil { - // log.Panic("Error parsing") + if err != nil || parseResult == nil { + return nil, fmt.Errorf("could not parse comments out of %s", *content.ContentName) } - if parseResult == nil || parseResult.Comments == nil { - return todoCommentBlocks - } + var todoCommentBlocks []TodoBlock = []TodoBlock{} for _, commentBlock := range parseResult.Comments { hasTodo := strings.Contains(strings.ToLower(commentBlock.String()), "todo") if hasTodo { todoCommentBlocks = append(todoCommentBlocks, TodoBlock{ CommentBlock: commentBlock, - FileName: path, + RawContent: &content, }) } } - return todoCommentBlocks + return &todoCommentBlocks, nil +} + +func (c TDRCore) getTodosCommentsFromFileOrDirectory(fileOrFolder string) (*[]TodoBlock, error) { + fileOrFolderInfo, err := os.Stat(fileOrFolder) + if err != nil { + return nil, fmt.Errorf("could not get stats of file or folder %s", fileOrFolder) + } + if fileOrFolderInfo.IsDir() { + panic("unimplemented") //todo(unimplemented): unimplemented + } + + if !fileOrFolderInfo.IsDir() { + contents, err := os.ReadFile(fileOrFolder) + if err != nil { + return nil, fmt.Errorf("could not read file %s", fileOrFolder) + } + + if !strings.Contains(fileOrFolder, ".") { + return nil, fmt.Errorf("file %s does not have a type", fileOrFolder) + } + filenameSegments := strings.Split(fileOrFolder, ".") + extension := filenameSegments[len(filenameSegments)-1] + extension = strings.ToLower(extension) + extension = strings.TrimSpace(extension) + + return c.getTodosFromContent(ContentType{ + Content: &contents, + ContentName: util.Ptr(fileOrFolderInfo.Name()), + ContentType: &extension, + }) + } + + panic("unimplemented") +} + +type ProcessedNamedContent struct { + Name *string + Processed []tdrl.TDRLTodo +} + +type ProcessOutput struct { + Todos []ProcessedNamedContent +} + +func (c TDRCore) Process(fileOrFolder string) (ProcessOutput, error) { + tdrlParser := tdrl.NewParser() + + todoCommentBlocks, err := c.getTodosCommentsFromFileOrDirectory(fileOrFolder) + if err != nil || todoCommentBlocks == nil { + return ProcessOutput{}, err + } + + processedTodoOutput := []ProcessedNamedContent{} + + for _, todoCommentBlock := range *todoCommentBlocks { + + if todoCommentBlock.CommentBlock == nil { + //todo(refactor): Not sure if this should happen. Need to check and possibly refactor + log.Print("Warning: comment block is nil\n") + continue + } + + todoLines := []string{} + for _, line := range todoCommentBlock.CommentBlock.Lines { + todoLines = append(todoLines, line.String()) + } + todoContent := strings.Join(todoLines, " ") + todoContent = strings.ToLower(todoContent) + + processedTodos := tdrlParser.ProcessTodo(todoContent) + processedTodoOutput = append(processedTodoOutput, ProcessedNamedContent{ + Name: todoCommentBlock.RawContent.ContentName, //todo(ptr): possible nil ptr deref + Processed: processedTodos, + }) + + } + + return ProcessOutput{ + Todos: processedTodoOutput, + }, nil } From 3ce7fb8287fdaf9ddb0437436995929b7cecee66 Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sat, 4 Oct 2025 14:29:03 +1000 Subject: [PATCH 11/15] basic lipgloss and bubbletea setup --- .gitignore | 4 +- .vscode/launch.json | 25 -------- .vscode/settings.json | 22 ------- .vscode/tasks.json | 118 ----------------------------------- cmd/dev-cli/main.go | 13 ++++ go.mod | 16 ++--- pkg/cli/cli.go | 116 +++++++++++++++++++++++++++++++++- pkg/cli/components/header.go | 29 +++++++++ pkg/cli/components/tags.go | 35 +++++++++++ pkg/cli/components/todos.go | 33 ++++++++++ 10 files changed, 235 insertions(+), 176 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/tasks.json create mode 100644 cmd/dev-cli/main.go create mode 100644 pkg/cli/components/header.go create mode 100644 pkg/cli/components/tags.go create mode 100644 pkg/cli/components/todos.go diff --git a/.gitignore b/.gitignore index b583e2f..b2d0d17 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,6 @@ dist/* __debug_bin* -.swp \ No newline at end of file +.swp + +.vscode \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 4ffd92c..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Package", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "${workspaceFolder}/cmd/dev", - // "console": "integratedTerminal", - // "args": ["-f", "main.go", "-l"] - }, - // { - // "name": "Test all", - // "type": "go", - // "request": "launch", - // "mode": "test", - // "program": "${workspaceFolder}", - // "args": [ - // "./..." - // ], - // "showLog": true, - // }, - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5660478..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "editor.inlineSuggest.edits.allowCodeShifting": "always", - "editor.inlineSuggest.edits.renderSideBySide": "auto", - "github.copilot.nextEditSuggestions.enabled": false, - "github.copilot.nextEditSuggestions.fixes": false, - // "go.toolsManagement.checkForUpdates": "local", - // "go.useLanguageServer": true, - // "go.formatTool": "goimports", - // "go.lintTool": "golint", - // "go.vetOnSave": "package", - // "go.buildOnSave": "package", - // "go.testOnSave": false, - // "go.coverOnSave": false, - // "go.gocodeAutoBuild": false, - // "go.buildTags": "", - // "go.testFlags": ["-v"], - // "go.testTimeout": "30s", - // "editor.formatOnSave": true, - // "editor.codeActionsOnSave": { - // "source.organizeImports": "explicit" - // } -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 8b94b8c..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,118 +0,0 @@ -// { -// "version": "2.0.0", -// "tasks": [ -// { -// "label": "go: build", -// "type": "shell", -// "command": "go", -// "args": [ -// "build", -// "-o", -// "todor-tool", -// "." -// ], -// "group": { -// "kind": "build", -// "isDefault": true -// }, -// "presentation": { -// "echo": true, -// "reveal": "always", -// "focus": false, -// "panel": "shared" -// }, -// "problemMatcher": [ -// "$go" -// ], -// "options": { -// "cwd": "${workspaceFolder}/tool" -// } -// }, -// { -// "label": "go: run", -// "type": "shell", -// "command": "go", -// "args": [ -// "run", -// "." -// ], -// "group": { -// "kind": "test", -// "isDefault": true -// }, -// "presentation": { -// "echo": true, -// "reveal": "always", -// "focus": false, -// "panel": "shared" -// }, -// "problemMatcher": [ -// "$go" -// ], -// "dependsOn": [], -// "options": { -// "cwd": "${workspaceFolder}/tool" -// } -// }, -// { -// "label": "go: test", -// "type": "shell", -// "command": "go", -// "args": [ -// "test", -// "-v", -// "./..." -// ], -// "group": "test", -// "presentation": { -// "echo": true, -// "reveal": "always", -// "focus": false, -// "panel": "shared" -// }, -// "problemMatcher": [ -// "$go" -// ], -// "options": { -// "cwd": "${workspaceFolder}/tool" -// } -// }, -// { -// "label": "go: clean", -// "type": "shell", -// "command": "go", -// "args": [ -// "clean" -// ], -// "group": "build", -// "presentation": { -// "echo": true, -// "reveal": "always", -// "focus": false, -// "panel": "shared" -// }, -// "options": { -// "cwd": "${workspaceFolder}/tool" -// } -// }, -// { -// "label": "go: mod tidy", -// "type": "shell", -// "command": "go", -// "args": [ -// "mod", -// "tidy" -// ], -// "group": "build", -// "presentation": { -// "echo": true, -// "reveal": "always", -// "focus": false, -// "panel": "shared" -// }, -// "options": { -// "cwd": "${workspaceFolder}/tool" -// } -// } -// ] -// } \ No newline at end of file diff --git a/cmd/dev-cli/main.go b/cmd/dev-cli/main.go new file mode 100644 index 0000000..20549e0 --- /dev/null +++ b/cmd/dev-cli/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/Tehhs/tdr/pkg/cli" +) + +func main() { + log.Print("hi") + + cli.New(cli.NewCLIParams{}) +} \ No newline at end of file diff --git a/go.mod b/go.mod index 0a7db78..b3c76b8 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,19 @@ go 1.24.0 toolchain go1.24.6 -require github.com/antlr4-go/antlr/v4 v4.13.1 +require ( + github.com/antlr4-go/antlr/v4 v4.13.1 + github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/tree-sitter/tree-sitter-bash v0.25.0 + github.com/tree-sitter/tree-sitter-c-sharp v0.23.1 + github.com/tree-sitter/tree-sitter-javascript v0.23.1 + github.com/tree-sitter/tree-sitter-typescript v0.23.2 +) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect @@ -23,10 +29,6 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/tree-sitter/tree-sitter-bash v0.25.0 // indirect - github.com/tree-sitter/tree-sitter-c-sharp v0.23.1 // indirect - github.com/tree-sitter/tree-sitter-javascript v0.23.1 // indirect - github.com/tree-sitter/tree-sitter-typescript v0.23.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.3.8 // indirect diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 42ed377..70462ef 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -1,6 +1,116 @@ package cli +import ( + "fmt" + "os" -type TdrCli struct { - -} \ No newline at end of file + components "github.com/Tehhs/tdr/pkg/cli/components" + "github.com/Tehhs/tdr/pkg/core" + "github.com/Tehhs/tdr/pkg/util" + tea "github.com/charmbracelet/bubbletea" + + "github.com/charmbracelet/lipgloss" +) + +type TdrCli struct { + TDRClient *core.TDRCore +} + +type NewCLIParams struct { +} + +const ( + screenTags = iota + screenTodos +) + +type BubbleTeaModel struct { + FileOrFolder *string + HeaderModel components.HeaderModel + TagsModel components.TagsModel + TodoModel components.TodoModel + ViewState int +} + +func initialModel() BubbleTeaModel { + return BubbleTeaModel{ + FileOrFolder: util.Ptr("examplefile.txt"), + TagsModel: components.NewTagsModel(), + TodoModel: components.NewTodoModel(), + HeaderModel: components.NewHeaderModel(), + } +} + +func (btm BubbleTeaModel) Init() tea.Cmd { + return nil +} + +func (m BubbleTeaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + case tea.KeyMsg: + + switch msg.String() { + + // These keys should exit the program. + case "ctrl+c", "q": + return m, tea.Quit + + //switch views + case "up", "down", "s": + if m.ViewState == screenTags { + m.ViewState = screenTodos + } else { + m.ViewState = screenTags + } + } + + } + + // Return the updated model to the Bubble Tea runtime for processing. + // Note that we're not returning a command. + return m, nil +} + +func (m BubbleTeaModel) View() string { + s := "" + + headerView := m.HeaderModel.View() + s += fmt.Sprintf("%s", headerView) + + var mainView string = "no main view selected" + switch m.ViewState { + case screenTags: + mainView = m.TagsModel.View() + case screenTodos: + mainView = m.TodoModel.View() + } + + s += fmt.Sprintf("\n\n%s\n\n", mainView) + + // The footer + footer := "\n\n[q - Quit] [s switch views]" + + + finalView := lipgloss.JoinVertical( + lipgloss.Left, + headerView, + mainView, + footer, + ) + + // Send the UI for rendering + return finalView +} + +func New(args NewCLIParams) *TdrCli { + + p := tea.NewProgram(initialModel()) + + if _, err := p.Run(); err != nil { + fmt.Printf("Alas, there's been an error: %v", err) + os.Exit(1) + } + + return nil +} diff --git a/pkg/cli/components/header.go b/pkg/cli/components/header.go new file mode 100644 index 0000000..cf0c9b4 --- /dev/null +++ b/pkg/cli/components/header.go @@ -0,0 +1,29 @@ +package components + +import ( + "fmt" + + tea "github.com/charmbracelet/bubbletea" +) + +type HeaderModel struct { + FileName string +} + +func (m HeaderModel) View() string { + s := fmt.Sprintf("File Name: %s\n\n", m.FileName) + + return s +} + +func (btm HeaderModel) Init() tea.Cmd { + return nil +} + +func (m HeaderModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + return m, nil +} + +func NewHeaderModel() HeaderModel { + return HeaderModel{} +} diff --git a/pkg/cli/components/tags.go b/pkg/cli/components/tags.go new file mode 100644 index 0000000..962c27f --- /dev/null +++ b/pkg/cli/components/tags.go @@ -0,0 +1,35 @@ +package components + +import ( + tea "github.com/charmbracelet/bubbletea" +) + +type TagsModel struct { + FileName string +} + +func (m TagsModel) View() string { + s := ` + +***************************************** +* +* THIS IS THE TAGS VIEW (WIP) +* +***************************************** + +` + + return s +} + +func (btm TagsModel) Init() tea.Cmd { + return nil +} + +func (m TagsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + return m, nil +} + +func NewTagsModel() TagsModel { + return TagsModel{} +} diff --git a/pkg/cli/components/todos.go b/pkg/cli/components/todos.go new file mode 100644 index 0000000..447b943 --- /dev/null +++ b/pkg/cli/components/todos.go @@ -0,0 +1,33 @@ +package components + +import ( + tea "github.com/charmbracelet/bubbletea" +) + +type TodoModel struct { + FileName string +} + +func (m TodoModel) View() string { + s := `***************************************** +* +* THIS IS THE TODO VIEW (WIP) +* +***************************************** + +` + + return s +} + +func (btm TodoModel) Init() tea.Cmd { + return nil +} + +func (m TodoModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + return m, nil +} + +func NewTodoModel() TodoModel { + return TodoModel{} +} From 3ea3ab19ad71c913e4be4417bb43bac982f9ea57 Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sun, 12 Oct 2025 15:54:45 +1100 Subject: [PATCH 12/15] saving lipgloss stuff not a real commit just need to save this for another computer --- go.mod | 5 +-- pkg/cli/components/tags.go | 63 +++++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index b3c76b8..1edab6e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/Tehhs/tdr -go 1.24.0 - -toolchain go1.24.6 +go 1.24.6 require ( github.com/antlr4-go/antlr/v4 v4.13.1 @@ -39,5 +37,4 @@ require ( github.com/tree-sitter/go-tree-sitter v0.25.0 github.com/tree-sitter/tree-sitter-go v0.23.4 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - ) diff --git a/pkg/cli/components/tags.go b/pkg/cli/components/tags.go index 962c27f..0de450b 100644 --- a/pkg/cli/components/tags.go +++ b/pkg/cli/components/tags.go @@ -1,23 +1,62 @@ package components import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + // "github.com/charmbracelet/lipgloss/table" ) +type Tag struct { + TagName string + Amt int +} + type TagsModel struct { FileName string + Tags []Tag + TagIndex int } +var ( + purple = lipgloss.Color("99") + gray = lipgloss.Color("245") + lightGray = lipgloss.Color("241") + + cellStyle = lipgloss.NewStyle().Padding(0, 1).Padding(1, 0) + oddRowStyle = cellStyle.Foreground(gray) + evenRowStyle = cellStyle.Foreground(lightGray) +) + func (m TagsModel) View() string { - s := ` + s := "" -***************************************** -* -* THIS IS THE TAGS VIEW (WIP) -* -***************************************** + + // amtPerRow := 2 + // rows := [][]string{} + // for i, item := range m.Tags { + // rowIndex := i / amtPerRow + // colIndex := i % amtPerRow + // rowIndex[rowIndex][colIndex] = item.TagName -` + // } + + + + // t := table.New(). + // Border(lipgloss.HiddenBorder()). + // Width(80). + // StyleFunc(func(row, col int) lipgloss.Style { + // switch { + // case row%2 == 0: + // return evenRowStyle + // default: + // return oddRowStyle + // } + // }) + // Rows(rows...) + + // s += t.Render() return s } @@ -31,5 +70,13 @@ func (m TagsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func NewTagsModel() TagsModel { - return TagsModel{} + + tagsModel := TagsModel{} + + tagsModel.Tags = []Tag{ + {TagName: "Tag1"}, + {TagName: "Tag2"}, + {TagName: "Tag3"}, + } + return tagsModel } From a9aedf98342f729991f02250e13f970f7fb72225 Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sat, 25 Oct 2025 13:09:42 +1100 Subject: [PATCH 13/15] feat(tui): wip scan on start --- pkg/cli/cli.cmds.go | 29 ++++++++++++++++++ pkg/cli/cli.go | 59 +++++++++++++++++++++++-------------- pkg/cli/cli.msgs.go | 10 +++++++ pkg/cli/cli.reqs.go | 4 +++ pkg/cli/cli.updates.go | 67 ++++++++++++++++++++++++++++++++++++++++++ pkg/core/core.go | 48 +++++++++++++++++------------- 6 files changed, 175 insertions(+), 42 deletions(-) create mode 100644 pkg/cli/cli.cmds.go create mode 100644 pkg/cli/cli.msgs.go create mode 100644 pkg/cli/cli.reqs.go create mode 100644 pkg/cli/cli.updates.go diff --git a/pkg/cli/cli.cmds.go b/pkg/cli/cli.cmds.go new file mode 100644 index 0000000..47f8c71 --- /dev/null +++ b/pkg/cli/cli.cmds.go @@ -0,0 +1,29 @@ +package cli + +import ( + "github.com/Tehhs/tdr/pkg/core" + tea "github.com/charmbracelet/bubbletea" +) + +func scanCmd(tdrc *core.TDRCore, filepath string) tea.Cmd { + + if tdrc == nil { + panic("tdr is not initialized") + } + + return func() tea.Msg { + + output, err := tdrc.Process(filepath) + + if err != nil { + return ScannedMsg{ + HasScanError: true, + ScanErrorMsg: "Could not scan for todos", + } + } + + return ScannedMsg{ + NewTodos: &output.Todos, + } + } +} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 70462ef..b6778d6 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -6,7 +6,6 @@ import ( components "github.com/Tehhs/tdr/pkg/cli/components" "github.com/Tehhs/tdr/pkg/core" - "github.com/Tehhs/tdr/pkg/util" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -19,52 +18,69 @@ type TdrCli struct { type NewCLIParams struct { } +type ScreenState int + const ( - screenTags = iota + screenTags ScreenState = iota screenTodos ) +type ScanState int + +const ( + ScanInProgress ScanState = iota + ScanFinished + ScanErrored + ScanHasntScanned +) + type BubbleTeaModel struct { FileOrFolder *string HeaderModel components.HeaderModel TagsModel components.TagsModel TodoModel components.TodoModel - ViewState int + + ViewState ScreenState + TDRCore *core.TDRCore + DisplayLoadingPage bool + IsLoading bool + ProcessedNamedContenet *[]core.ProcessedNamedContent } func initialModel() BubbleTeaModel { + + coreInstance := core.NewTDRCore() + // processOutput, err := coreInstance.Process("./main.go") + return BubbleTeaModel{ - FileOrFolder: util.Ptr("examplefile.txt"), - TagsModel: components.NewTagsModel(), - TodoModel: components.NewTodoModel(), - HeaderModel: components.NewHeaderModel(), + TagsModel: components.NewTagsModel(), + TodoModel: components.NewTodoModel(), + HeaderModel: components.NewHeaderModel(), + TDRCore: coreInstance, + DisplayLoadingPage: true, + IsLoading: true, } } func (btm BubbleTeaModel) Init() tea.Cmd { - return nil + return scanCmd(btm.TDRCore, "/") } func (m BubbleTeaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: + return m.update_keyMsg(msg) - switch msg.String() { + case ScanRequest: + return m.update_scanRequest(msg) - // These keys should exit the program. - case "ctrl+c", "q": - return m, tea.Quit - - //switch views - case "up", "down", "s": - if m.ViewState == screenTags { - m.ViewState = screenTodos - } else { - m.ViewState = screenTags - } - } + case ScannedMsg: + return m.update_scannedMessage(msg) + // default: + // there can be other bubble tea events so dont emit errors here + // slog.Error("unknown update message") } // Return the updated model to the Bubble Tea runtime for processing. @@ -91,7 +107,6 @@ func (m BubbleTeaModel) View() string { // The footer footer := "\n\n[q - Quit] [s switch views]" - finalView := lipgloss.JoinVertical( lipgloss.Left, headerView, diff --git a/pkg/cli/cli.msgs.go b/pkg/cli/cli.msgs.go new file mode 100644 index 0000000..6187180 --- /dev/null +++ b/pkg/cli/cli.msgs.go @@ -0,0 +1,10 @@ +package cli + +import "github.com/Tehhs/tdr/pkg/core" + +type ScannedMsg struct { + HasScanError bool + ScanErrorMsg string + + NewTodos *[]core.ProcessedNamedContent +} diff --git a/pkg/cli/cli.reqs.go b/pkg/cli/cli.reqs.go new file mode 100644 index 0000000..edeec8d --- /dev/null +++ b/pkg/cli/cli.reqs.go @@ -0,0 +1,4 @@ +package cli + +// For scan request children pass to parent +type ScanRequest struct{} diff --git a/pkg/cli/cli.updates.go b/pkg/cli/cli.updates.go new file mode 100644 index 0000000..495252a --- /dev/null +++ b/pkg/cli/cli.updates.go @@ -0,0 +1,67 @@ +package cli + +import ( + "log/slog" + + tea "github.com/charmbracelet/bubbletea" +) + +func (m BubbleTeaModel) update_scanRequest(msg tea.Msg) (tea.Model, tea.Cmd) { + _, ok := msg.(ScanRequest) + if !ok { + slog.Error("invalid scan request") + return m, nil + } + + return m, scanCmd(m.TDRCore, "/") + +} + +func (m BubbleTeaModel) update_scannedMessage(msg tea.Msg) (tea.Model, tea.Cmd) { + m.IsLoading = false + + scannedMessage, ok := msg.(ScannedMsg) + + if !ok { + slog.Error("invalid scanned message") + return m, nil + } + + if scannedMessage.HasScanError { + slog.Error(scannedMessage.ScanErrorMsg) + //Maybe display this error + return m, nil + } + + m.ProcessedNamedContenet = scannedMessage.NewTodos + + return m, nil + +} + +func (m BubbleTeaModel) update_keyMsg(msg tea.Msg) (tea.Model, tea.Cmd) { + var keyMsg *tea.KeyMsg = nil + if km, ok := msg.(tea.KeyMsg); ok { + keyMsg = &km + } else { + slog.Error("call to update_keyMsg with non tea.KeyMsg type arg") + return m, nil + } + + switch keyMsg.String() { + + // These keys should exit the program. + case "ctrl+c", "q": + return m, tea.Quit + + //switch views + case "up", "down", "s": + if m.ViewState == screenTags { + m.ViewState = screenTodos + } else { + m.ViewState = screenTags + } + } + + return m, nil +} diff --git a/pkg/core/core.go b/pkg/core/core.go index 9bb137f..deb4378 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -1,8 +1,10 @@ package core import ( + "errors" "fmt" "log" + "log/slog" "os" "strings" @@ -14,8 +16,8 @@ import ( type TDRCore struct { } -func NewTDRCore() TDRCore { - return TDRCore{} +func NewTDRCore() *TDRCore { + return &TDRCore{} } // todo(refactor): not sure if this should be here @@ -41,7 +43,7 @@ type ContentType struct { } func (c TDRCore) getTodosFromContent(content ContentType) (*[]TodoBlock, error) { - if content.Content == nil { + if content.Content == nil { return nil, fmt.Errorf("cannot process todo content with no actual content") } @@ -83,7 +85,10 @@ func (c TDRCore) getTodosCommentsFromFileOrDirectory(fileOrFolder string) (*[]To return nil, fmt.Errorf("could not get stats of file or folder %s", fileOrFolder) } if fileOrFolderInfo.IsDir() { - panic("unimplemented") //todo(unimplemented): unimplemented + slog.Error("unimplemented") + return nil, errors.New("unimplemented") + // panic("unimplemented") //todo(unimplemented): unimplemented + //todo(unimplemented): need to implement this } if !fileOrFolderInfo.IsDir() { @@ -91,8 +96,8 @@ func (c TDRCore) getTodosCommentsFromFileOrDirectory(fileOrFolder string) (*[]To if err != nil { return nil, fmt.Errorf("could not read file %s", fileOrFolder) } - - if !strings.Contains(fileOrFolder, ".") { + + if !strings.Contains(fileOrFolder, ".") { return nil, fmt.Errorf("file %s does not have a type", fileOrFolder) } filenameSegments := strings.Split(fileOrFolder, ".") @@ -110,33 +115,36 @@ func (c TDRCore) getTodosCommentsFromFileOrDirectory(fileOrFolder string) (*[]To panic("unimplemented") } -type ProcessedNamedContent struct { - Name *string +type ProcessedNamedContent struct { + //Named content. Usually a filename. + Name *string + + //List of processed todos. Processed []tdrl.TDRLTodo } -type ProcessOutput struct { - Todos []ProcessedNamedContent +type ProcessOutput struct { + Todos []ProcessedNamedContent //todo(performance): make pointer } -func (c TDRCore) Process(fileOrFolder string) (ProcessOutput, error) { +func (c TDRCore) Process(fileOrFolder string) (ProcessOutput, error) { tdrlParser := tdrl.NewParser() - + todoCommentBlocks, err := c.getTodosCommentsFromFileOrDirectory(fileOrFolder) - if err != nil || todoCommentBlocks == nil { + if err != nil || todoCommentBlocks == nil { return ProcessOutput{}, err } processedTodoOutput := []ProcessedNamedContent{} - for _, todoCommentBlock := range *todoCommentBlocks { - + for _, todoCommentBlock := range *todoCommentBlocks { + if todoCommentBlock.CommentBlock == nil { //todo(refactor): Not sure if this should happen. Need to check and possibly refactor log.Print("Warning: comment block is nil\n") - continue + continue } - + todoLines := []string{} for _, line := range todoCommentBlock.CommentBlock.Lines { todoLines = append(todoLines, line.String()) @@ -146,13 +154,13 @@ func (c TDRCore) Process(fileOrFolder string) (ProcessOutput, error) { processedTodos := tdrlParser.ProcessTodo(todoContent) processedTodoOutput = append(processedTodoOutput, ProcessedNamedContent{ - Name: todoCommentBlock.RawContent.ContentName, //todo(ptr): possible nil ptr deref + Name: todoCommentBlock.RawContent.ContentName, //todo(ptr): possible nil ptr deref Processed: processedTodos, }) - + } return ProcessOutput{ Todos: processedTodoOutput, - }, nil + }, nil } From d5c354fcc5ae59321575a04a1d5a3043e5c50b41 Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sat, 25 Oct 2025 13:50:23 +1100 Subject: [PATCH 14/15] feat(tests): tests for util and tdrl packages --- pkg/tdrl/tdrl_test.go | 49 ++++++++++++++++++++++++++++++++++++++++ pkg/util/util.go | 15 +++++++++++++ pkg/util/util_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 pkg/tdrl/tdrl_test.go create mode 100644 pkg/util/util_test.go diff --git a/pkg/tdrl/tdrl_test.go b/pkg/tdrl/tdrl_test.go new file mode 100644 index 0000000..9eaf41e --- /dev/null +++ b/pkg/tdrl/tdrl_test.go @@ -0,0 +1,49 @@ +package tdrl + +import ( + "testing" + + "github.com/Tehhs/tdr/pkg/util" +) + +func Test_BasicTodos(t *testing.T) { + tdrlParser := NewParser() + + todos := tdrlParser.ProcessTodo("todo: this is a basic todo") + + if len(todos) != 1 { + t.Error("could not process todos") + } + +} + +func Test_Tags(t *testing.T) { + tests := []struct { + name string + content string + want []string + }{ + { + name: "Simple Tags", + content: "todo(tag1, tag2, tag3): this is a todo", + want: []string{"tag1", "tag2", "tag3"}, + }, + //More here when we work out what exactly we like in tags + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewParser() + got := p.ProcessTodo(tt.content) + + if len(got) != 1 { + t.Errorf("Got %d amount of todos returned from parsing '%s'; Should be 1", len(got), tt.content) + } + + todo := got[0] + + if !util.ArraysEqual(todo.Tags, tt.want) { + t.Errorf("Tags for '%s' = %+v, want %+v", tt.content, todo.Tags, tt.want) + } + }) + } +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 0aa9abd..b8b50d4 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -10,4 +10,19 @@ func PtrVal[T any](t *T) T { func Ptr[T any](t T) *T { return &t +} + +func ArraysEqual[T comparable](ar1 []T, ar2 []T) bool { + if len(ar1) != len(ar2) { + return false + } + + for i, v1 := range ar1 { + v2 := ar2[i] + if v1 != v2 { + return false + } + } + + return true } \ No newline at end of file diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go new file mode 100644 index 0000000..3fe971e --- /dev/null +++ b/pkg/util/util_test.go @@ -0,0 +1,52 @@ +package util + +import "testing" + + +func TestArraysEqual(t *testing.T) { + + //Test: Simple test to make sure simple arrays that equal do equal + + equal := ArraysEqual([]string{ + "a", "b", "c", + }, []string{ + "a", "b", "c", + }) + + if !equal { + t.Error(`["a", "b", "c"] should match ["a", "b", "c"]`) + } + + //Test: Make sure length is taken into consideration + + equal = ArraysEqual([]int{ + 1, 2, 3, + }, []int{ + 1, 2, 3, 4, + }) + + if equal { + t.Error(`[1, 2, 3] should not match [1, 2, 3, 4]`) + } + + equal = ArraysEqual([]int{ + 1, 2, 3, 4, + }, []int{ + 1, 2, 3, + }) + + if equal { + t.Error(`[1, 2, 3, 4] should not match [1, 2, 3]`) + } + + //Test: If value is somehow not taken into consideration somehow I dont even know... + equal = ArraysEqual([]int{ + 1, 2, 3, + }, []int{ + 4, 5, 6, + }) + + if equal { + t.Error(`[1, 2, 3] should not match [4, 5, 6]`) + } +} From fc97c784bbc0b4355e1d00a662bf4761f5f83d98 Mon Sep 17 00:00:00 2001 From: Tehhs Date: Sat, 25 Oct 2025 14:06:10 +1100 Subject: [PATCH 15/15] feat(tests): more simple tests --- pkg/tdrl/tdrl_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pkg/tdrl/tdrl_test.go b/pkg/tdrl/tdrl_test.go index 9eaf41e..dec3a23 100644 --- a/pkg/tdrl/tdrl_test.go +++ b/pkg/tdrl/tdrl_test.go @@ -1,6 +1,7 @@ package tdrl import ( + "fmt" "testing" "github.com/Tehhs/tdr/pkg/util" @@ -17,6 +18,39 @@ func Test_BasicTodos(t *testing.T) { } +func Test_ProcessedText(t *testing.T) { + p := NewParser() + + //Simple + + text := "this should work" + todos := p.ProcessTodo(fmt.Sprintf("todo: %s", text)) + + if len(todos) != 1 { + t.Error("returned no todos or invalid amount of todos") + } + + todo := todos[0] + + if todo.ProcessedContent != text { + t.Errorf("Failed to process content. Got '%s', wanted '%s'", todo.ProcessedContent, text) + } + + //Probably want to add more test for if there's no leading space like "todo:this should work" returning "this should work" + + //todo(test): Need to make sure processed text works with tags too +} + +func Test_WithoutTags(t *testing.T) { + p := NewParser() + text := "this should work" + todos := p.ProcessTodo(fmt.Sprintf("todo: %s", text)) + + if len(todos) != 1 { + t.Error("returned no todos or invalid amount of todos") + } +} + func Test_Tags(t *testing.T) { tests := []struct { name string