Skip to content

Commit 449b0b2

Browse files
authored
Adaptive attrmap (#12)
* Working on adaptive attrmap * Working on color mode * Add CLI arg to control color mode * ci: use GHC 9.12.2 in matrix
1 parent e4aef8d commit 449b0b2

File tree

6 files changed

+142
-73
lines changed

6 files changed

+142
-73
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
include:
15-
- ghc: "9.6.6"
15+
- ghc: "9.12.2"
1616
yaml: "stack.yaml"
1717

1818
steps:

app/Main.hs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import Sauron.Setup.ReposFromConfigFile
3333
import Sauron.Setup.ReposFromCurrentDirectory
3434
import Sauron.Types
3535
import Sauron.UI
36-
import Sauron.UI.AttrMap
36+
import Sauron.UI.AttrMap (buildAdaptiveAttrMap)
3737
import System.IO.Error (userError)
3838
import UnliftIO.Async
3939
import UnliftIO.Concurrent
@@ -47,19 +47,19 @@ refreshPeriod = 100000
4747
defaultHealthCheckPeriodUs :: PeriodSpec
4848
defaultHealthCheckPeriodUs = PeriodSpec (1_000_000 * 60 * 10)
4949

50-
app :: App AppState AppEvent ClickableName
51-
app = App {
50+
mkApp :: V.ColorMode -> App AppState AppEvent ClickableName
51+
mkApp colorMode = App {
5252
appDraw = drawUI
5353
, appChooseCursor = showFirstCursor
5454
, appHandleEvent = \event -> get >>= \s -> appEvent s event
5555
, appStartEvent = return ()
56-
, appAttrMap = const mainAttrMap
56+
, appAttrMap = const (buildAdaptiveAttrMap colorMode)
5757
}
5858

5959

6060
main :: IO ()
6161
main = do
62-
CliArgs {cliConfigFile, cliShowAllRepos} <- parseCliArgs
62+
CliArgs {cliConfigFile, cliShowAllRepos, cliColorMode} <- parseCliArgs
6363

6464
baseContext@(BaseContext {..}) <- buildBaseContext
6565

@@ -97,6 +97,8 @@ main = do
9797

9898
, _appForm = Nothing
9999
, _appAnimationCounter = 0
100+
101+
, _appColorMode = V.FullColor
100102
}
101103

102104

@@ -123,14 +125,14 @@ main = do
123125
threadDelay refreshPeriod
124126

125127
let buildVty = do
126-
v <- V.mkVty V.defaultConfig
127-
let output = V.outputIface v
128-
when (V.supportsMode output V.Mouse) $
129-
V.setMode output V.Mouse True
128+
v <- V.userConfig >>= V.mkVty
129+
when (V.supportsMode (V.outputIface v) V.Mouse) $
130+
V.setMode (V.outputIface v) V.Mouse True
130131
return v
131132
initialVty <- buildVty
133+
let colorMode = fromMaybe (V.outputColorMode (V.outputIface initialVty)) cliColorMode
132134
flip onException (cancel eventAsync) $
133-
void $ customMain initialVty buildVty (Just eventChan) app initialState
135+
void $ customMain initialVty buildVty (Just eventChan) (mkApp colorMode) (initialState { _appColorMode = colorMode })
134136

135137

136138
buildBaseContext :: IO BaseContext

app/Sauron/Event.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ getNodeUrl _repoBaseUrl (SingleJobNode (EntityData {_state})) = case fetchableCu
175175
Nothing -> ""
176176
getNodeUrl repoBaseUrl (SingleBranchNode (EntityData {_static=branch})) = repoBaseUrl <> "/tree/" <> toString (branchName branch)
177177
getNodeUrl repoBaseUrl (SingleCommitNode (EntityData {_static=commit})) = repoBaseUrl <> "/commit/" <> toString (untagName (commitSha commit))
178-
getNodeUrl _repoBaseUrl (SingleNotificationNode (EntityData {_static=notification})) =
178+
getNodeUrl _repoBaseUrl (SingleNotificationNode (EntityData {_static=_notification})) =
179179
-- We need to make API calls to get proper html_url fields, but getNodeUrl is pure
180180
-- This should be handled asynchronously when the notification is opened
181181
"placeholder://notification-url"

app/Sauron/Options.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Data.Aeson as A
2222
import Data.Aeson.TH
2323
import Data.String.Interpolate
2424
import Data.Text as T
25+
import qualified Graphics.Vty as V
2526
import Options.Applicative
2627
import Relude
2728
import Sauron.Aeson
@@ -36,6 +37,7 @@ data CliArgs = CliArgs {
3637
, cliDebugFile :: Maybe FilePath
3738
, cliForceAuth :: Bool
3839
, cliShowAllRepos :: Bool
40+
, cliColorMode :: Maybe V.ColorMode
3941
} deriving (Show)
4042

4143
cliArgsParser :: Parser CliArgs
@@ -46,6 +48,7 @@ cliArgsParser = CliArgs
4648
<*> optional (strOption (long "debug-file" <> help "Debug file path (for optional logging)" <> metavar "STRING"))
4749
<*> switch (long "auth" <> help "Force OAuth authentication flow")
4850
<*> switch (long "all" <> help "Show all repositories for the authenticated user")
51+
<*> optional (option (maybeReader parseColorMode) (long "color-mode" <> help "Force a specific color mode (full, 240, 16, 8, none)" <> metavar "MODE"))
4952

5053
parseCliArgs :: IO CliArgs
5154
parseCliArgs = execParser opts
@@ -56,6 +59,16 @@ parseCliArgs = execParser opts
5659
<> header "hello - a test for optparse-applicative"
5760
)
5861

62+
-- * Color mode
63+
64+
parseColorMode :: String -> Maybe V.ColorMode
65+
parseColorMode "full" = Just V.FullColor
66+
parseColorMode "240" = Just (V.ColorMode240 240)
67+
parseColorMode "16" = Just V.ColorMode16
68+
parseColorMode "8" = Just V.ColorMode8
69+
parseColorMode "none" = Just V.NoColor
70+
parseColorMode _ = Nothing
71+
5972
-- * Config file
6073

6174
newtype PeriodSpec = PeriodSpec Int

src/Sauron/Types.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Data.Time
2020
import Data.Typeable
2121
import qualified Data.Vector as V
2222
import GitHub hiding (Status)
23+
import qualified Graphics.Vty as V
2324
import Lens.Micro
2425
import Lens.Micro.TH
2526
import Network.HTTP.Client (Manager)
@@ -354,6 +355,8 @@ data AppState = AppState {
354355

355356
, _appForm :: Maybe (Form Text AppEvent ClickableName, Int)
356357
, _appAnimationCounter :: Int
358+
359+
, _appColorMode :: V.ColorMode
357360
}
358361

359362
makeLenses ''AppState

src/Sauron/UI/AttrMap.hs

Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,75 +16,71 @@ import qualified Skylighting.Styles as Sky
1616
mkAttrName :: String -> AttrName
1717
mkAttrName = attrName
1818

19-
mainAttrMap :: AttrMap
20-
mainAttrMap = attrMap V.defAttr ([
21-
-- (listAttr, V.white `on` V.blue)
22-
-- (listSelectedAttr, V.blue `on` V.white)
23-
-- (listSelectedAttr, bg (V.Color240 $ V.rgbColorToColor240 0 1 0))
24-
-- (selectedAttr, bg (V.Color240 $ V.rgbColorToColor240 0 1 0))
25-
19+
buildAdaptiveAttrMap :: V.ColorMode -> AttrMap
20+
buildAdaptiveAttrMap colorMode = attrMap V.defAttr ([
2621
-- Statuses
2722
(iconAttr, fg V.white)
2823
, (normalAttr, fg V.white)
29-
, (notFetchedAttr, fg midGray)
24+
, (notFetchedAttr, fg (select midGray))
3025
, (fetchingAttr, fg V.blue)
3126
, (erroredAttr, fg V.red)
3227

3328
-- Pagination
34-
35-
, (searchAttr, fg midGray)
36-
, (selectedPageAttr, fg solarizedGreen & flip V.withStyle V.bold)
37-
, (notSelectedPageAttr, fg midGray & flip V.withStyle V.dim)
38-
, (pageEllipsesAttr, fg midGray)
29+
, (searchAttr, fg (select midGray))
30+
, (selectedPageAttr, fg (select solarizedGreen) & flip V.withStyle V.bold)
31+
, (notSelectedPageAttr, fg (select midGray) & flip V.withStyle V.dim)
32+
, (pageEllipsesAttr, fg (select midGray))
3933

4034
-- Stats box
4135
, (starsAttr, fg V.yellow)
4236

4337
-- Workflow icons
44-
, (cancelledAttr, fg midGray)
38+
, (cancelledAttr, fg (select midGray))
4539
, (greenCheckAttr, fg V.green)
4640
, (redXAttr, fg V.red)
47-
, (ellipsesAttr, fg midGray)
48-
, (neutralAttr, fg midGray)
41+
, (ellipsesAttr, fg (select midGray))
42+
, (neutralAttr, fg (select midGray))
4943
, (unknownAttr, fg V.white)
50-
, (queuedAttr, fg githubOrange)
44+
, (queuedAttr, fg (select githubOrange))
5145

5246
-- Progress bar
53-
, (progressCompleteAttr, bg (V.Color240 235))
54-
, (progressIncompleteAttr, bg (V.Color240 225))
47+
, (progressCompleteAttr, bg (select (V.Color240 235, V.Color240 235, V.brightBlack, V.black, V.black)))
48+
, (progressIncompleteAttr, bg (select (V.Color240 225, V.Color240 225, V.black, V.black, V.black)))
5549

5650
-- Main list
57-
, (toggleMarkerAttr, fg midGray)
58-
, (openMarkerAttr, fg midGray)
51+
, (toggleMarkerAttr, fg (select midGray))
52+
, (openMarkerAttr, fg (select midGray))
5953

6054
-- Hotkey stuff
6155
, (hotkeyAttr, fg V.blue)
62-
, (disabledHotkeyAttr, fg midGray)
63-
, (hotkeyMessageAttr, fg brightWhite)
64-
, (disabledHotkeyMessageAttr, fg brightGray)
56+
, (disabledHotkeyAttr, fg (select midGray))
57+
, (hotkeyMessageAttr, fg (select brightWhite))
58+
, (disabledHotkeyMessageAttr, fg (select brightGray))
6559

6660
-- Spinner
67-
, (circleSpinnerAttr, fg brightGray)
61+
, (circleSpinnerAttr, fg (select brightGray))
6862

6963
-- General UI
70-
, (branchAttr, fg solarizedBlue)
71-
, (hashAttr, fg midGray)
72-
, (hashNumberAttr, fg solarizedViolet)
73-
, (usernameAttr, fg solarizedBlue)
64+
, (branchAttr, fg (select solarizedBlue))
65+
, (hashAttr, fg (select midGray))
66+
, (hashNumberAttr, fg (select solarizedViolet))
67+
, (usernameAttr, fg (select solarizedBlue))
7468

7569
-- Markdown
7670
, (italicText, style V.italic)
7771
, (underlineText, style V.underline)
7872
, (boldText, style V.bold)
7973
, (boldUnderlineText, V.defAttr `V.withStyle` V.bold `V.withStyle` V.underline)
8074
, (strikeoutText, style V.strikethrough)
81-
, (codeText, brightWhite `on` dimGray)
82-
, (codeBlockText, fg midGray)
83-
, (horizontalRuleAttr, fg midGray)
75+
, (codeText, select brightWhite `on` select dimGray)
76+
, (codeBlockText, fg (select midGray))
77+
, (horizontalRuleAttr, fg (select midGray))
8478

8579
-- Forms
8680
, (E.editFocusedAttr, V.black `on` V.yellow)
8781
] <> attrMappingsForStyle Sky.breezeDark)
82+
where
83+
select = selectColor colorMode
8884

8985
iconAttr = mkAttrName "icon"
9086
normalAttr = mkAttrName "normal"
@@ -141,32 +137,87 @@ codeText = mkAttrName "code-text"
141137
codeBlockText = mkAttrName "code-block-text"
142138
horizontalRuleAttr = mkAttrName "horizontal-rule"
143139

144-
-- * Colors
145-
146-
solarizedBase03 = V.rgbColor 0x00 0x2b 0x36
147-
solarizedBase02 = V.rgbColor 0x07 0x36 0x42
148-
solarizedBase01 = V.rgbColor 0x58 0x6e 0x75
149-
solarizedbase00 = V.rgbColor 0x65 0x7b 0x83
150-
solarizedBase0 = V.rgbColor 0x83 0x94 0x96
151-
solarizedBase1 = V.rgbColor 0x93 0xa1 0xa1
152-
solarizedBase2 = V.rgbColor 0xee 0xe8 0xd5
153-
solarizedBase3 = V.rgbColor 0xfd 0xf6 0xe3
154-
solarizedYellow = V.rgbColor 0xb5 0x89 0x00
155-
solarizedOrange = V.rgbColor 0xcb 0x4b 0x16
156-
solarizedRed = V.rgbColor 0xdc 0x32 0x2f
157-
solarizedMagenta = V.rgbColor 0xd3 0x36 0x82
158-
solarizedViolet = V.rgbColor 0x6c 0x71 0xc4
159-
solarizedBlue = V.rgbColor 0x26 0x8b 0xd2
160-
solarizedCyan = V.rgbColor 0x2a 0xa1 0x98
161-
solarizedGreen = V.rgbColor 0x85 0x99 0x00
162-
163-
githubOrange = V.rgbColor 0xd2 0x99 0x22
164-
165-
dimGray = grayAt 25
166-
midGray = grayAt 50
167-
brightGray = grayAt 80
168-
midWhite = grayAt 140
169-
brightWhite = grayAt 200
170-
171-
grayAt level = V.rgbColor level level level
172-
-- grayAt level = V.Color240 $ V.rgbColorToColor240 level level level
140+
-- * Color Fallback System
141+
142+
-- Type alias for color fallbacks: (FullColor, ColorMode240, ColorMode16, ColorMode8, NoColor)
143+
type ColorFallback = (V.Color, V.Color, V.Color, V.Color, V.Color)
144+
145+
-- Color selection function based on detected terminal capabilities
146+
selectColor :: V.ColorMode -> ColorFallback -> V.Color
147+
selectColor colorMode (fullColor, color240, color16, color8, noColor) = case colorMode of
148+
V.FullColor -> fullColor
149+
V.ColorMode240 _ -> color240
150+
V.ColorMode16 -> color16
151+
V.ColorMode8 -> color8
152+
V.NoColor -> noColor
153+
154+
-- * Color Definitions with Fallbacks
155+
156+
solarizedBase03 :: ColorFallback
157+
solarizedBase03 = (V.rgbColor 0x00 0x2b 0x36, V.Color240 234, V.black, V.black, V.black)
158+
159+
solarizedBase02 :: ColorFallback
160+
solarizedBase02 = (V.rgbColor 0x07 0x36 0x42, V.Color240 235, V.black, V.black, V.black)
161+
162+
solarizedBase01 :: ColorFallback
163+
solarizedBase01 = (V.rgbColor 0x58 0x6e 0x75, V.Color240 240, V.brightBlack, V.black, V.black)
164+
165+
solarizedbase00 :: ColorFallback
166+
solarizedbase00 = (V.rgbColor 0x65 0x7b 0x83, V.Color240 241, V.brightBlack, V.black, V.black)
167+
168+
solarizedBase0 :: ColorFallback
169+
solarizedBase0 = (V.rgbColor 0x83 0x94 0x96, V.Color240 244, V.brightBlack, V.white, V.white)
170+
171+
solarizedBase1 :: ColorFallback
172+
solarizedBase1 = (V.rgbColor 0x93 0xa1 0xa1, V.Color240 245, V.brightWhite, V.white, V.white)
173+
174+
solarizedBase2 :: ColorFallback
175+
solarizedBase2 = (V.rgbColor 0xee 0xe8 0xd5, V.Color240 254, V.brightWhite, V.white, V.white)
176+
177+
solarizedBase3 :: ColorFallback
178+
solarizedBase3 = (V.rgbColor 0xfd 0xf6 0xe3, V.Color240 230, V.brightWhite, V.white, V.white)
179+
180+
solarizedYellow :: ColorFallback
181+
solarizedYellow = (V.rgbColor 0xb5 0x89 0x00, V.Color240 136, V.brightYellow, V.yellow, V.white)
182+
183+
solarizedOrange :: ColorFallback
184+
solarizedOrange = (V.rgbColor 0xcb 0x4b 0x16, V.Color240 166, V.brightRed, V.red, V.white)
185+
186+
solarizedRed :: ColorFallback
187+
solarizedRed = (V.rgbColor 0xdc 0x32 0x2f, V.Color240 160, V.brightRed, V.red, V.white)
188+
189+
solarizedMagenta :: ColorFallback
190+
solarizedMagenta = (V.rgbColor 0xd3 0x36 0x82, V.Color240 125, V.brightMagenta, V.magenta, V.white)
191+
192+
solarizedViolet :: ColorFallback
193+
solarizedViolet = (V.rgbColor 0x6c 0x71 0xc4, V.Color240 61, V.brightBlue, V.blue, V.white)
194+
195+
solarizedBlue :: ColorFallback
196+
solarizedBlue = (V.rgbColor 0x26 0x8b 0xd2, V.Color240 33, V.brightBlue, V.blue, V.white)
197+
198+
solarizedCyan :: ColorFallback
199+
solarizedCyan = (V.rgbColor 0x2a 0xa1 0x98, V.Color240 37, V.brightCyan, V.cyan, V.white)
200+
201+
solarizedGreen :: ColorFallback
202+
solarizedGreen = (V.rgbColor 0x85 0x99 0x00, V.Color240 106, V.brightGreen, V.green, V.white)
203+
204+
githubOrange :: ColorFallback
205+
githubOrange = (V.rgbColor 0xd2 0x99 0x22, V.Color240 178, V.brightYellow, V.yellow, V.white)
206+
207+
dimGray :: ColorFallback
208+
dimGray = (grayAtRGB 25, V.Color240 236, V.black, V.black, V.black)
209+
210+
midGray :: ColorFallback
211+
midGray = (grayAtRGB 50, V.Color240 238, V.brightBlack, V.black, V.black)
212+
213+
brightGray :: ColorFallback
214+
brightGray = (grayAtRGB 80, V.Color240 244, V.brightBlack, V.white, V.white)
215+
216+
midWhite :: ColorFallback
217+
midWhite = (grayAtRGB 140, V.Color240 249, V.brightWhite, V.white, V.white)
218+
219+
brightWhite :: ColorFallback
220+
brightWhite = (grayAtRGB 200, V.Color240 253, V.brightWhite, V.white, V.white)
221+
222+
grayAtRGB :: Word8 -> V.Color
223+
grayAtRGB level = V.rgbColor level level level

0 commit comments

Comments
 (0)