From b6ae8daa5b6a16de960145a489212dc11bd9aa4f Mon Sep 17 00:00:00 2001 From: Joshua Rich Date: Wed, 18 Oct 2023 09:34:28 +1000 Subject: [PATCH] feat(app,keytracker)!: restructure corrections code - move check and correction logic into keytracker package. - use an interface to expose agent methods to keytracker. --- internal/app/app.go | 55 +++++++-------------- internal/handler/handler.go | 25 ---------- internal/keytracker/keytracker.go | 80 +++++++++++++++++++++++-------- internal/word/word.go | 21 -------- 4 files changed, 78 insertions(+), 103 deletions(-) delete mode 100644 internal/handler/handler.go delete mode 100644 internal/word/word.go diff --git a/internal/app/app.go b/internal/app/app.go index 87a0b6f..6fb94fc 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -17,9 +17,7 @@ import ( "fyne.io/fyne/v2" "github.com/joshuar/autocorrector/internal/corrections" "github.com/joshuar/autocorrector/internal/db" - "github.com/joshuar/autocorrector/internal/handler" "github.com/joshuar/autocorrector/internal/keytracker" - "github.com/joshuar/autocorrector/internal/word" "github.com/rs/zerolog/log" ) @@ -44,70 +42,53 @@ type App struct { tray fyne.Window Name, Version string showNotifications bool + notificationsCh chan *keytracker.Correction Done chan struct{} } +func (a *App) ShowNotifications() bool { + return a.showNotifications +} + +func (a *App) NotificationCh() chan *keytracker.Correction { + return a.notificationsCh +} + func New() *App { return &App{ app: newUI(), Name: Name, Version: Version, showNotifications: false, + notificationsCh: make(chan *keytracker.Correction), Done: make(chan struct{}), } } func (a *App) Run() { appCtx, cancelfunc := context.WithCancel(context.Background()) - handler := handler.NewHandler() if err := createDir(configPath); err != nil { log.Fatal().Err(err).Msg("Could not create config directory.") } - correctionsList, err := corrections.NewCorrections() - if err != nil { - log.Fatal().Err(err).Msg("Failed to open corrections file.") - } stats, err := db.RunStats(appCtx, configPath) defer close(stats.Done) if err != nil { log.Fatal().Err(err).Msg("Failed to start stats tracking.") } - keyTracker = keytracker.NewKeyTracker(handler.WordCh, stats) + if err := keytracker.NewKeyTracker(a, stats); err != nil { + log.Fatal().Err(err).Msg("Failed to start key tracking.") + } go func() { for { select { case <-appCtx.Done(): return - case notification := <-handler.NotificationsCh: - if a.showNotifications { - a.app.SendNotification(¬ification) - } - } - } - }() - - go func() { - for newWord := range handler.WordCh { - log.Debug().Msgf("Checking word: %s", newWord.Word) - stats.IncCheckedCounter() - if correction, ok := correctionsList.CheckWord(newWord.Word); ok { - handler.CorrectionCh <- word.WordDetails{ - Word: newWord.Word, - Correction: correction, - Punct: newWord.Punct, - } - } - } - }() - - go func() { - for correction := range handler.CorrectionCh { - keyTracker.CorrectWord(correction) - stats.IncCorrectedCounter() - handler.NotificationsCh <- fyne.Notification{ - Title: "Correction!", - Content: fmt.Sprintf("Corrected %s with %s", correction.Word, correction.Correction), + case n := <-a.notificationsCh: + a.app.SendNotification(&fyne.Notification{ + Title: "Correction!", + Content: fmt.Sprintf("Corrected %s with %s", n.Word, n.Correction), + }) } } }() diff --git a/internal/handler/handler.go b/internal/handler/handler.go deleted file mode 100644 index bf2dbf6..0000000 --- a/internal/handler/handler.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2023 Joshua Rich -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -package handler - -import ( - "fyne.io/fyne/v2" - "github.com/joshuar/autocorrector/internal/word" -) - -type handler struct { - WordCh chan word.WordDetails - CorrectionCh chan word.WordDetails - NotificationsCh chan fyne.Notification -} - -func NewHandler() *handler { - return &handler{ - WordCh: make(chan word.WordDetails), - CorrectionCh: make(chan word.WordDetails), - NotificationsCh: make(chan fyne.Notification), - } -} diff --git a/internal/keytracker/keytracker.go b/internal/keytracker/keytracker.go index 04a8fc4..c3eb20f 100644 --- a/internal/keytracker/keytracker.go +++ b/internal/keytracker/keytracker.go @@ -10,12 +10,30 @@ import ( "unicode" "unicode/utf8" + "github.com/joshuar/autocorrector/internal/corrections" "github.com/joshuar/autocorrector/internal/db" - "github.com/joshuar/autocorrector/internal/word" kbd "github.com/joshuar/gokbd" "github.com/rs/zerolog/log" ) +type agent interface { + ShowNotifications() bool + NotificationCh() chan *Correction +} + +type Correction struct { + Word, Correction string + Punct rune +} + +func NewCorrection(word, correction string, punct rune) *Correction { + return &Correction{ + Word: word, + Correction: correction, + Punct: punct, + } +} + // KeyTracker holds the channels for handling key presses and // indicating when word/line delimiter characters are encountered or // backspace is pressed @@ -25,7 +43,7 @@ type KeyTracker struct { paused bool } -func (kt *KeyTracker) slurpWords(wordCh chan word.WordDetails, stats *db.Stats) { +func (kt *KeyTracker) slurpWords(wordCh chan *Correction, stats *db.Stats) { charBuf := new(bytes.Buffer) patternBuf := newPatternBuf(3) log.Debug().Msg("Slurping words...") @@ -55,10 +73,7 @@ func (kt *KeyTracker) slurpWords(wordCh chan word.WordDetails, stats *db.Stats) // handle that stats.IncKeyCounter() if charBuf.Len() > 0 { - wordCh <- word.WordDetails{ - Word: charBuf.String(), - Punct: k.AsRune, - } + wordCh <- NewCorrection(charBuf.String(), "", k.AsRune) charBuf.Reset() } default: @@ -76,18 +91,35 @@ func (kt *KeyTracker) slurpWords(wordCh chan word.WordDetails, stats *db.Stats) kt.CloseKeyTracker() } -func (kt *KeyTracker) CorrectWord(correction word.WordDetails) { - if !kt.paused { - log.Debug().Msgf("Making correction %s to %s", correction.Word, correction.Correction) +func (kt *KeyTracker) checkWord(wordCh chan *Correction, correctionCh chan *Correction, corrections *corrections.Corrections, stats *db.Stats) { + for w := range wordCh { + log.Debug().Msgf("Checking word: %s", w.Word) + stats.IncCheckedCounter() + var ok bool + if w.Correction, ok = corrections.CheckWord(w.Word); ok { + correctionCh <- w + } + } +} + +func (kt *KeyTracker) correctWord(correctionCh chan *Correction, agent agent, stats *db.Stats) { + for correction := range correctionCh { + if !kt.paused { + log.Debug().Msgf("Making correction %s to %s", correction.Word, correction.Correction) - // Erase the existing word. - // Effectively, hit backspace key for the length of the word plus the punctuation mark. - for i := 0; i <= utf8.RuneCountInString(correction.Word); i++ { - kt.kbd.TypeBackspace() + // Erase the existing word. + // Effectively, hit backspace key for the length of the word plus the punctuation mark. + for i := 0; i <= utf8.RuneCountInString(correction.Word); i++ { + kt.kbd.TypeBackspace() + } + // Insert the replacement. + // Type out the replacement and whatever punctuation/delimiter was after it. + kt.kbd.TypeString(correction.Correction + string(correction.Punct)) + } + stats.IncCorrectedCounter() + if agent.ShowNotifications() { + agent.NotificationCh() <- correction } - // Insert the replacement. - // Type out the replacement and whatever punctuation/delimiter was after it. - kt.kbd.TypeString(correction.Correction + string(correction.Punct)) } } @@ -101,17 +133,25 @@ func (kt *KeyTracker) CloseKeyTracker() { } // NewKeyTracker creates a new keyTracker struct -func NewKeyTracker(wordCh chan word.WordDetails, stats *db.Stats) *KeyTracker { +func NewKeyTracker(agent agent, stats *db.Stats) error { vKbd, err := kbd.NewVirtualKeyboard("autocorrector") if err != nil { - log.Error().Err(err).Msg("Could not open a new virtual keyboard.") - return nil + return err } kt := &KeyTracker{ kbd: vKbd, kbdEvents: kbd.SnoopAllKeyboards(kbd.OpenAllKeyboardDevices()), paused: false, } + correctionsList, err := corrections.NewCorrections() + if err != nil { + return err + } + + correctionCh := make(chan *Correction) + wordCh := make(chan *Correction) go kt.slurpWords(wordCh, stats) - return kt + go kt.checkWord(wordCh, correctionCh, correctionsList, stats) + go kt.correctWord(correctionCh, agent, stats) + return nil } diff --git a/internal/word/word.go b/internal/word/word.go deleted file mode 100644 index fb56ef7..0000000 --- a/internal/word/word.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2023 Joshua Rich -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -package word - -type WordDetails struct { - Word, Correction string - Punct rune -} - -// NewWord creates a struct to hold a word, its correction and the -// punctuation mark that follows it -func NewWord(w string, c string, p rune) *WordDetails { - return &WordDetails{ - Word: w, - Correction: c, - Punct: p, - } -}