1{-# LANGUAGE OverloadedStrings #-}
2module Matterhorn.Themes
3  ( InternalTheme(..)
4
5  , defaultTheme
6  , internalThemes
7  , lookupTheme
8  , themeDocs
9
10  -- * Attribute names
11  , currentUserAttr
12  , timeAttr
13  , channelHeaderAttr
14  , channelListHeaderAttr
15  , currentChannelNameAttr
16  , unreadChannelAttr
17  , unreadGroupMarkerAttr
18  , mentionsChannelAttr
19  , currentTeamAttr
20  , urlAttr
21  , codeAttr
22  , emailAttr
23  , emojiAttr
24  , channelNameAttr
25  , clientMessageAttr
26  , clientHeaderAttr
27  , strikeThroughAttr
28  , clientEmphAttr
29  , clientStrongAttr
30  , dateTransitionAttr
31  , pinnedMessageIndicatorAttr
32  , newMessageTransitionAttr
33  , gapMessageAttr
34  , errorMessageAttr
35  , helpAttr
36  , helpEmphAttr
37  , channelSelectPromptAttr
38  , channelSelectMatchAttr
39  , completionAlternativeListAttr
40  , completionAlternativeCurrentAttr
41  , permalinkAttr
42  , dialogAttr
43  , dialogEmphAttr
44  , recentMarkerAttr
45  , replyParentAttr
46  , loadMoreAttr
47  , urlListSelectedAttr
48  , messageSelectAttr
49  , messageSelectStatusAttr
50  , urlSelectStatusAttr
51  , misspellingAttr
52  , editedMarkingAttr
53  , editedRecentlyMarkingAttr
54  , tabSelectedAttr
55  , tabUnselectedAttr
56  , buttonAttr
57  , buttonFocusedAttr
58
59  -- * Username formatting
60  , colorUsername
61  , attrForUsername
62  , usernameColorHashBuckets
63  )
64where
65
66import           Prelude ()
67import           Matterhorn.Prelude
68
69import           Brick
70import           Brick.Themes
71import           Brick.Widgets.List
72import qualified Brick.Widgets.FileBrowser as FB
73import           Brick.Widgets.Skylighting ( attrNameForTokenType
74                                           , attrMappingsForStyle
75                                           , highlightedCodeBlockAttr
76                                           )
77import           Brick.Forms ( focusedFormInputAttr )
78import           Data.Hashable ( hash )
79import qualified Data.Map as M
80import qualified Data.Text as T
81import           Graphics.Vty
82import qualified Skylighting.Styles as Sky
83import           Skylighting.Types ( TokenType(..) )
84
85import           Matterhorn.Types ( InternalTheme(..), specialUserMentions )
86
87
88helpAttr :: AttrName
89helpAttr = "help"
90
91helpEmphAttr :: AttrName
92helpEmphAttr = "helpEmphasis"
93
94recentMarkerAttr :: AttrName
95recentMarkerAttr = "recentChannelMarker"
96
97replyParentAttr :: AttrName
98replyParentAttr = "replyParentPreview"
99
100pinnedMessageIndicatorAttr :: AttrName
101pinnedMessageIndicatorAttr = "pinnedMessageIndicator"
102
103loadMoreAttr :: AttrName
104loadMoreAttr = "loadMoreMessages"
105
106urlListSelectedAttr :: AttrName
107urlListSelectedAttr = "urlListCursor"
108
109messageSelectAttr :: AttrName
110messageSelectAttr = "messageSelectCursor"
111
112editedMarkingAttr :: AttrName
113editedMarkingAttr = "editedMarking"
114
115editedRecentlyMarkingAttr :: AttrName
116editedRecentlyMarkingAttr = "editedRecentlyMarking"
117
118permalinkAttr :: AttrName
119permalinkAttr = "permalink"
120
121dialogAttr :: AttrName
122dialogAttr = "dialog"
123
124dialogEmphAttr :: AttrName
125dialogEmphAttr = "dialogEmphasis"
126
127channelSelectMatchAttr :: AttrName
128channelSelectMatchAttr = "channelSelectMatch"
129
130channelSelectPromptAttr :: AttrName
131channelSelectPromptAttr = "channelSelectPrompt"
132
133completionAlternativeListAttr :: AttrName
134completionAlternativeListAttr = "tabCompletionAlternative"
135
136completionAlternativeCurrentAttr :: AttrName
137completionAlternativeCurrentAttr = "tabCompletionCursor"
138
139timeAttr :: AttrName
140timeAttr = "time"
141
142currentUserAttr :: AttrName
143currentUserAttr = "currentUser"
144
145channelHeaderAttr :: AttrName
146channelHeaderAttr = "channelHeader"
147
148channelListHeaderAttr :: AttrName
149channelListHeaderAttr = "channelListSectionHeader"
150
151currentChannelNameAttr :: AttrName
152currentChannelNameAttr = "currentChannelName"
153
154channelNameAttr :: AttrName
155channelNameAttr = "channelName"
156
157unreadChannelAttr :: AttrName
158unreadChannelAttr = "unreadChannel"
159
160unreadGroupMarkerAttr :: AttrName
161unreadGroupMarkerAttr = "unreadChannelGroupMarker"
162
163mentionsChannelAttr :: AttrName
164mentionsChannelAttr = "channelWithMentions"
165
166currentTeamAttr :: AttrName
167currentTeamAttr = "currentTeam"
168
169tabSelectedAttr :: AttrName
170tabSelectedAttr = "tabSelected"
171
172tabUnselectedAttr :: AttrName
173tabUnselectedAttr = "tabUnselected"
174
175dateTransitionAttr :: AttrName
176dateTransitionAttr = "dateTransition"
177
178newMessageTransitionAttr :: AttrName
179newMessageTransitionAttr = "newMessageTransition"
180
181urlAttr :: AttrName
182urlAttr = "url"
183
184codeAttr :: AttrName
185codeAttr = "codeBlock"
186
187emailAttr :: AttrName
188emailAttr = "email"
189
190emojiAttr :: AttrName
191emojiAttr = "emoji"
192
193clientMessageAttr :: AttrName
194clientMessageAttr = "clientMessage"
195
196clientHeaderAttr :: AttrName
197clientHeaderAttr = "markdownHeader"
198
199strikeThroughAttr :: AttrName
200strikeThroughAttr = "markdownStrikethrough"
201
202clientEmphAttr :: AttrName
203clientEmphAttr = "markdownEmph"
204
205clientStrongAttr :: AttrName
206clientStrongAttr = "markdownStrong"
207
208errorMessageAttr :: AttrName
209errorMessageAttr = "errorMessage"
210
211gapMessageAttr :: AttrName
212gapMessageAttr = "gapMessage"
213
214misspellingAttr :: AttrName
215misspellingAttr = "misspelling"
216
217messageSelectStatusAttr :: AttrName
218messageSelectStatusAttr = "messageSelectStatus"
219
220urlSelectStatusAttr :: AttrName
221urlSelectStatusAttr = "urlSelectStatus"
222
223buttonAttr :: AttrName
224buttonAttr = "button"
225
226buttonFocusedAttr :: AttrName
227buttonFocusedAttr = buttonAttr <> "focused"
228
229lookupTheme :: Text -> Maybe InternalTheme
230lookupTheme n = find ((== n) . internalThemeName) internalThemes
231
232internalThemes :: [InternalTheme]
233internalThemes = validateInternalTheme <$>
234    [ darkColorTheme
235    , darkColor256Theme
236    , lightColorTheme
237    , lightColor256Theme
238    ]
239
240validateInternalTheme :: InternalTheme -> InternalTheme
241validateInternalTheme it =
242    let un = undocumentedAttrNames (internalTheme it)
243    in if not $ null un
244       then error $ "Internal theme " <> show (T.unpack (internalThemeName it)) <>
245                    " references undocumented attribute names: " <> show un
246       else it
247
248undocumentedAttrNames :: Theme -> [AttrName]
249undocumentedAttrNames t =
250    let noDocs k = isNothing $ attrNameDescription themeDocs k
251    in filter noDocs (M.keys $ themeDefaultMapping t)
252
253defaultTheme :: InternalTheme
254defaultTheme = darkColorTheme
255
256lightColorTheme :: InternalTheme
257lightColorTheme = InternalTheme name theme desc
258    where
259        theme = newTheme def $ lightAttrs usernameColors16
260        name = "builtin:light"
261        def = black `on` white
262        desc = "A color theme for terminal windows with light background colors"
263
264lightColor256Theme :: InternalTheme
265lightColor256Theme = InternalTheme name theme desc
266    where
267        theme = newTheme def $ lightAttrs usernameColors256
268        name = "builtin:light256"
269        def = black `on` white
270        desc = "Like builtin:light, but with 256-color username colors"
271
272lightAttrs :: [Attr] -> [(AttrName, Attr)]
273lightAttrs usernameColors =
274    let sty = Sky.kate
275    in [ (timeAttr,                         fg black)
276       , (buttonAttr,                       black `on` cyan)
277       , (buttonFocusedAttr,                black `on` yellow)
278       , (currentUserAttr,                  defAttr `withStyle` bold)
279       , (channelHeaderAttr,                fg black)
280       , (channelListHeaderAttr,            fg cyan)
281       , (currentChannelNameAttr,           black `on` yellow `withStyle` bold)
282       , (unreadChannelAttr,                black `on` cyan   `withStyle` bold)
283       , (unreadGroupMarkerAttr,            fg black `withStyle` bold)
284       , (mentionsChannelAttr,              black `on` red    `withStyle` bold)
285       , (urlAttr,                          fg brightYellow)
286       , (emailAttr,                        fg yellow)
287       , (codeAttr,                         fg magenta)
288       , (emojiAttr,                        fg yellow)
289       , (channelNameAttr,                  fg blue)
290       , (clientMessageAttr,                fg black)
291       , (clientEmphAttr,                   fg black `withStyle` bold)
292       , (clientStrongAttr,                 fg black `withStyle` bold `withStyle` underline)
293       , (clientHeaderAttr,                 fg red `withStyle` bold)
294       , (strikeThroughAttr,                defAttr `withStyle` strikethrough)
295       , (dateTransitionAttr,               fg green)
296       , (newMessageTransitionAttr,         black `on` yellow)
297       , (errorMessageAttr,                 fg red)
298       , (gapMessageAttr,                   fg red)
299       , (helpAttr,                         fg black)
300       , (pinnedMessageIndicatorAttr,       black `on` cyan)
301       , (helpEmphAttr,                     fg blue `withStyle` bold)
302       , (channelSelectMatchAttr,           black `on` magenta `withStyle` underline)
303       , (channelSelectPromptAttr,          fg black)
304       , (completionAlternativeListAttr,    white `on` blue)
305       , (completionAlternativeCurrentAttr, black `on` yellow)
306       , (dialogAttr,                       black `on` cyan)
307       , (dialogEmphAttr,                   fg white)
308       , (permalinkAttr,                    fg green)
309       , (listSelectedFocusedAttr,          black `on` yellow)
310       , (recentMarkerAttr,                 fg black `withStyle` bold)
311       , (loadMoreAttr,                     black `on` cyan)
312       , (urlListSelectedAttr,              black `on` yellow)
313       , (messageSelectAttr,                black `on` yellow)
314       , (messageSelectStatusAttr,          fg black)
315       , (urlSelectStatusAttr,              fg black)
316       , (misspellingAttr,                  fg red `withStyle` underline)
317       , (editedMarkingAttr,                fg yellow)
318       , (editedRecentlyMarkingAttr,        black `on` yellow)
319       , (tabSelectedAttr,                  black `on` yellow)
320       , (focusedFormInputAttr,             black `on` yellow)
321       , (currentTeamAttr,                  black `on` yellow)
322       , (FB.fileBrowserCurrentDirectoryAttr, white `on` blue)
323       , (FB.fileBrowserSelectionInfoAttr,  white `on` blue)
324       , (FB.fileBrowserDirectoryAttr,      fg blue)
325       , (FB.fileBrowserBlockDeviceAttr,    fg magenta)
326       , (FB.fileBrowserCharacterDeviceAttr, fg green)
327       , (FB.fileBrowserNamedPipeAttr,      fg yellow)
328       , (FB.fileBrowserSymbolicLinkAttr,   fg cyan)
329       , (FB.fileBrowserUnixSocketAttr,     fg red)
330       ] <>
331       ((\(i, a) -> (usernameAttr i, a)) <$> zip [0..usernameColorHashBuckets-1] (cycle usernameColors)) <>
332       (filter skipBaseCodeblockAttr $ attrMappingsForStyle sty)
333
334darkAttrs :: [Attr] -> [(AttrName, Attr)]
335darkAttrs usernameColors =
336  let sty = Sky.espresso
337  in [ (timeAttr,                         fg white)
338     , (buttonAttr,                       black `on` cyan)
339     , (buttonFocusedAttr,                black `on` yellow)
340     , (currentUserAttr,                  defAttr `withStyle` bold)
341     , (channelHeaderAttr,                fg white)
342     , (channelListHeaderAttr,            fg cyan)
343     , (currentChannelNameAttr,           black `on` yellow `withStyle` bold)
344     , (unreadChannelAttr,                black `on` cyan   `withStyle` bold)
345     , (unreadGroupMarkerAttr,            fg white `withStyle` bold)
346     , (mentionsChannelAttr,              black `on` brightMagenta `withStyle` bold)
347     , (urlAttr,                          fg yellow)
348     , (emailAttr,                        fg yellow)
349     , (codeAttr,                         fg magenta)
350     , (emojiAttr,                        fg yellow)
351     , (channelNameAttr,                  fg cyan)
352     , (pinnedMessageIndicatorAttr,       fg cyan `withStyle` bold)
353     , (clientMessageAttr,                fg white)
354     , (clientEmphAttr,                   fg white `withStyle` bold)
355     , (clientStrongAttr,                 fg white `withStyle` bold `withStyle` underline)
356     , (clientHeaderAttr,                 fg red `withStyle` bold)
357     , (strikeThroughAttr,                defAttr `withStyle` strikethrough)
358     , (dateTransitionAttr,               fg green)
359     , (newMessageTransitionAttr,         fg yellow `withStyle` bold)
360     , (errorMessageAttr,                 fg red)
361     , (gapMessageAttr,                   black `on` yellow)
362     , (helpAttr,                         fg white)
363     , (helpEmphAttr,                     fg cyan `withStyle` bold)
364     , (channelSelectMatchAttr,           black `on` magenta `withStyle` underline)
365     , (channelSelectPromptAttr,          fg white)
366     , (completionAlternativeListAttr,    white `on` blue)
367     , (completionAlternativeCurrentAttr, black `on` yellow)
368     , (dialogAttr,                       black `on` cyan)
369     , (dialogEmphAttr,                   fg white)
370     , (permalinkAttr,                    fg brightCyan)
371     , (listSelectedFocusedAttr,          black `on` yellow)
372     , (recentMarkerAttr,                 fg yellow `withStyle` bold)
373     , (loadMoreAttr,                     black `on` cyan)
374     , (urlListSelectedAttr,              black `on` yellow)
375     , (messageSelectAttr,                black `on` yellow)
376     , (messageSelectStatusAttr,          fg white)
377     , (urlSelectStatusAttr,              fg white)
378     , (misspellingAttr,                  fg red `withStyle` underline)
379     , (editedMarkingAttr,                fg yellow)
380     , (editedRecentlyMarkingAttr,        black `on` yellow)
381     , (tabSelectedAttr,                  black `on` yellow)
382     , (focusedFormInputAttr,             black `on` yellow)
383     , (currentTeamAttr,                  black `on` yellow)
384     , (FB.fileBrowserCurrentDirectoryAttr, white `on` blue)
385     , (FB.fileBrowserSelectionInfoAttr,  white `on` blue)
386     , (FB.fileBrowserDirectoryAttr,      fg blue)
387     , (FB.fileBrowserBlockDeviceAttr,    fg magenta)
388     , (FB.fileBrowserCharacterDeviceAttr, fg green)
389     , (FB.fileBrowserNamedPipeAttr,      fg yellow)
390     , (FB.fileBrowserSymbolicLinkAttr,   fg cyan)
391     , (FB.fileBrowserUnixSocketAttr,     fg red)
392     ] <>
393     ((\(i, a) -> (usernameAttr i, a)) <$> zip [0..usernameColorHashBuckets-1] (cycle usernameColors)) <>
394     (filter skipBaseCodeblockAttr $ attrMappingsForStyle sty)
395
396skipBaseCodeblockAttr :: (AttrName, Attr) -> Bool
397skipBaseCodeblockAttr = ((/= highlightedCodeBlockAttr) . fst)
398
399darkColorTheme :: InternalTheme
400darkColorTheme = InternalTheme name theme desc
401    where
402        theme = newTheme def $ darkAttrs usernameColors16
403        name = "builtin:dark"
404        def = defAttr
405        desc = "A color theme for terminal windows with dark background colors"
406
407darkColor256Theme :: InternalTheme
408darkColor256Theme = InternalTheme name theme desc
409    where
410        theme = newTheme def $ darkAttrs usernameColors256
411        name = "builtin:dark256"
412        def = defAttr
413        desc = "Like builtin:dark, but with 256-color username colors"
414
415usernameAttr :: Int -> AttrName
416usernameAttr i = "username" <> (attrName $ show i)
417
418-- | Render a string with a color chosen based on the text of a
419-- username.
420--
421-- This function takes some display text and renders it using an
422-- attribute based on the username associated with the text. If the
423-- username associated with the text is equal to the username of
424-- the user running Matterhorn, the display text is formatted with
425-- 'currentAttr'. Otherwise it is formatted with an attribute chosen
426-- by hashing the associated username and choosing from amongst the
427-- username color hash buckets with 'usernameAttr'.
428--
429-- Usually the first argument to this function will be @myUsername st@,
430-- where @st@ is a 'ChatState'.
431--
432-- The most common way to call this function is
433--
434-- @colorUsername (myUsername st) u u
435--
436-- The third argument is allowed to vary from the second since sometimes
437-- we call this with the user's status sigil as the third argument.
438colorUsername :: Text
439              -- ^ The username for the user currently running
440              -- Matterhorn
441              -> Text
442              -- ^ The username associated with the text to render
443              -> Text
444              -- ^ The text to render
445              -> Widget a
446colorUsername current username display =
447    let aName = attrForUsername username
448        maybeWithCurrentAttr = if current == username
449                               then withAttr currentUserAttr
450                               else id
451    in withDefAttr aName $
452       maybeWithCurrentAttr $
453       txt (display)
454
455-- | Return the attribute name to use for the specified username.
456-- The input username is expected to be the username only (i.e. no
457-- sigil).
458--
459-- If the input username is a special reserved username such as "all",
460-- the @clientEmphAttr@ attribute name will be returned. Otherwise
461-- a hash-bucket username attribute name will be returned based on
462-- the hash value of the username and the number of hash buckets
463-- (@usernameColorHashBuckets@).
464attrForUsername :: Text
465                -- ^ The username to get an attribute for
466                -> AttrName
467attrForUsername username =
468    let normalizedUsername = T.toLower username
469        aName = if normalizedUsername `elem` specialUserMentions
470                then clientEmphAttr
471                else usernameAttr h
472        h = hash normalizedUsername `mod` usernameColorHashBuckets
473    in aName
474
475-- | The number of hash buckets to use when hashing usernames to choose
476-- their colors.
477usernameColorHashBuckets :: Int
478usernameColorHashBuckets = 50
479
480usernameColors16 :: [Attr]
481usernameColors16 =
482    [ fg red
483    , fg green
484    , fg yellow
485    , fg blue
486    , fg magenta
487    , fg cyan
488    , fg brightRed
489    , fg brightGreen
490    , fg brightYellow
491    , fg brightBlue
492    , fg brightMagenta
493    , fg brightCyan
494    ]
495
496usernameColors256 :: [Attr]
497usernameColors256 = mkColor <$> username256ColorChoices
498    where
499        mkColor (r, g, b) = defAttr `withForeColor` rgbColor r g b
500
501username256ColorChoices :: [(Integer, Integer, Integer)]
502username256ColorChoices =
503    [ (255, 0, 86)
504    , (158, 0, 142)
505    , (14, 76, 161)
506    , (255, 229, 2)
507    , (149, 0, 58)
508    , (255, 147, 126)
509    , (164, 36, 0)
510    , (98, 14, 0)
511    , (0, 0, 255)
512    , (106, 130, 108)
513    , (0, 174, 126)
514    , (194, 140, 159)
515    , (0, 143, 156)
516    , (95, 173, 78)
517    , (255, 2, 157)
518    , (255, 116, 163)
519    , (152, 255, 82)
520    , (167, 87, 64)
521    , (254, 137, 0)
522    , (1, 208, 255)
523    , (187, 136, 0)
524    , (117, 68, 177)
525    , (165, 255, 210)
526    , (122, 71, 130)
527    , (0, 71, 84)
528    , (181, 0, 255)
529    , (144, 251, 146)
530    , (189, 211, 147)
531    , (229, 111, 254)
532    , (222, 255, 116)
533    , (0, 255, 120)
534    , (0, 155, 255)
535    , (0, 100, 1)
536    , (0, 118, 255)
537    , (133, 169, 0)
538    , (0, 185, 23)
539    , (120, 130, 49)
540    , (0, 255, 198)
541    , (255, 110, 65)
542    ]
543
544-- Functions for dealing with Skylighting styles
545
546attrNameDescription :: ThemeDocumentation -> AttrName -> Maybe Text
547attrNameDescription td an = M.lookup an (themeDescriptions td)
548
549themeDocs :: ThemeDocumentation
550themeDocs = ThemeDocumentation $ M.fromList $
551    [ ( timeAttr
552      , "Timestamps on chat messages"
553      )
554    , ( channelHeaderAttr
555      , "Channel headers displayed above chat message lists"
556      )
557    , ( channelListHeaderAttr
558      , "The heading of the channel list sections"
559      )
560    , ( currentChannelNameAttr
561      , "The currently selected channel in the channel list"
562      )
563    , ( unreadChannelAttr
564      , "A channel in the channel list with unread messages"
565      )
566    , ( unreadGroupMarkerAttr
567      , "The channel group marker indicating unread messages"
568      )
569    , ( mentionsChannelAttr
570      , "A channel in the channel list with unread mentions"
571      )
572    , ( urlAttr
573      , "A URL in a chat message"
574      )
575    , ( codeAttr
576      , "A code block in a chat message with no language indication"
577      )
578    , ( emailAttr
579      , "An e-mail address in a chat message"
580      )
581    , ( emojiAttr
582      , "A text emoji indication in a chat message"
583      )
584    , ( channelNameAttr
585      , "A channel name in a chat message"
586      )
587    , ( clientMessageAttr
588      , "A Matterhorn diagnostic or informative message"
589      )
590    , ( clientHeaderAttr
591      , "Markdown heading"
592      )
593    , ( strikeThroughAttr
594      , "Markdown strikethrough text"
595      )
596    , ( clientEmphAttr
597      , "Markdown 'emphasized' text"
598      )
599    , ( clientStrongAttr
600      , "Markdown 'strong' text"
601      )
602    , ( dateTransitionAttr
603      , "Date transition lines between chat messages on different days"
604      )
605    , ( pinnedMessageIndicatorAttr
606      , "The indicator for messages that have been pinned"
607      )
608    , ( newMessageTransitionAttr
609      , "The 'New Messages' line that appears above unread messages"
610      )
611    , ( tabSelectedAttr
612      , "Selected tabs in tabbed windows"
613      )
614    , ( tabUnselectedAttr
615      , "Unselected tabs in tabbed windows"
616      )
617    , ( errorMessageAttr
618      , "Matterhorn error messages"
619      )
620    , ( gapMessageAttr
621      , "Matterhorn message gap information"
622      )
623    , ( helpAttr
624      , "The help screen text"
625      )
626    , ( helpEmphAttr
627      , "The help screen's emphasized text"
628      )
629    , ( channelSelectPromptAttr
630      , "Channel selection: prompt"
631      )
632    , ( channelSelectMatchAttr
633      , "Channel selection: the portion of a channel name that matches"
634      )
635    , ( completionAlternativeListAttr
636      , "Tab completion alternatives"
637      )
638    , ( completionAlternativeCurrentAttr
639      , "The currently-selected tab completion alternative"
640      )
641    , ( permalinkAttr
642      , "A post permalink"
643      )
644    , ( dialogAttr
645      , "Dialog box text"
646      )
647    , ( dialogEmphAttr
648      , "Dialog box emphasized text"
649      )
650    , ( recentMarkerAttr
651      , "The marker indicating the channel last visited"
652      )
653    , ( replyParentAttr
654      , "The first line of parent messages appearing above reply messages"
655      )
656    , ( loadMoreAttr
657      , "The 'Load More' line that appears at the top of a chat message list"
658      )
659    , ( urlListSelectedAttr
660      , "URL list: the selected URL"
661      )
662    , ( messageSelectAttr
663      , "Message selection: the currently-selected message"
664      )
665    , ( messageSelectStatusAttr
666      , "Message selection: the message selection actions"
667      )
668    , ( urlSelectStatusAttr
669      , "Link selection: the message selection actions"
670      )
671    , ( misspellingAttr
672      , "A misspelled word in the chat message editor"
673      )
674    , ( editedMarkingAttr
675      , "The 'edited' marking that appears on edited messages"
676      )
677    , ( editedRecentlyMarkingAttr
678      , "The 'edited' marking that appears on newly-edited messages"
679      )
680    , ( highlightedCodeBlockAttr
681      , "The base attribute for syntax-highlighted code blocks"
682      )
683    , ( attrNameForTokenType KeywordTok
684      , "Syntax highlighting: Keyword"
685      )
686    , ( attrNameForTokenType DataTypeTok
687      , "Syntax highlighting: DataType"
688      )
689    , ( attrNameForTokenType DecValTok
690      , "Syntax highlighting: Declaration"
691      )
692    , ( attrNameForTokenType BaseNTok
693      , "Syntax highlighting: BaseN"
694      )
695    , ( attrNameForTokenType FloatTok
696      , "Syntax highlighting: Float"
697      )
698    , ( attrNameForTokenType ConstantTok
699      , "Syntax highlighting: Constant"
700      )
701    , ( attrNameForTokenType CharTok
702      , "Syntax highlighting: Char"
703      )
704    , ( attrNameForTokenType SpecialCharTok
705      , "Syntax highlighting: Special Char"
706      )
707    , ( attrNameForTokenType StringTok
708      , "Syntax highlighting: String"
709      )
710    , ( attrNameForTokenType VerbatimStringTok
711      , "Syntax highlighting: Verbatim String"
712      )
713    , ( attrNameForTokenType SpecialStringTok
714      , "Syntax highlighting: Special String"
715      )
716    , ( attrNameForTokenType ImportTok
717      , "Syntax highlighting: Import"
718      )
719    , ( attrNameForTokenType CommentTok
720      , "Syntax highlighting: Comment"
721      )
722    , ( attrNameForTokenType DocumentationTok
723      , "Syntax highlighting: Documentation"
724      )
725    , ( attrNameForTokenType AnnotationTok
726      , "Syntax highlighting: Annotation"
727      )
728    , ( attrNameForTokenType CommentVarTok
729      , "Syntax highlighting: Comment"
730      )
731    , ( attrNameForTokenType OtherTok
732      , "Syntax highlighting: Other"
733      )
734    , ( attrNameForTokenType FunctionTok
735      , "Syntax highlighting: Function"
736      )
737    , ( attrNameForTokenType VariableTok
738      , "Syntax highlighting: Variable"
739      )
740    , ( attrNameForTokenType ControlFlowTok
741      , "Syntax highlighting: Control Flow"
742      )
743    , ( attrNameForTokenType OperatorTok
744      , "Syntax highlighting: Operator"
745      )
746    , ( attrNameForTokenType BuiltInTok
747      , "Syntax highlighting: Built-In"
748      )
749    , ( attrNameForTokenType ExtensionTok
750      , "Syntax highlighting: Extension"
751      )
752    , ( attrNameForTokenType PreprocessorTok
753      , "Syntax highlighting: Preprocessor"
754      )
755    , ( attrNameForTokenType AttributeTok
756      , "Syntax highlighting: Attribute"
757      )
758    , ( attrNameForTokenType RegionMarkerTok
759      , "Syntax highlighting: Region Marker"
760      )
761    , ( attrNameForTokenType InformationTok
762      , "Syntax highlighting: Information"
763      )
764    , ( attrNameForTokenType WarningTok
765      , "Syntax highlighting: Warning"
766      )
767    , ( attrNameForTokenType AlertTok
768      , "Syntax highlighting: Alert"
769      )
770    , ( attrNameForTokenType ErrorTok
771      , "Syntax highlighting: Error"
772      )
773    , ( attrNameForTokenType NormalTok
774      , "Syntax highlighting: Normal text"
775      )
776    , ( listSelectedFocusedAttr
777      , "The selected channel"
778      )
779    , ( focusedFormInputAttr
780      , "A form input that has focus"
781      )
782    , ( FB.fileBrowserAttr
783      , "The base file browser attribute"
784      )
785    , ( FB.fileBrowserCurrentDirectoryAttr
786      , "The file browser current directory attribute"
787      )
788    , ( FB.fileBrowserSelectionInfoAttr
789      , "The file browser selection information attribute"
790      )
791    , ( FB.fileBrowserDirectoryAttr
792      , "Attribute for directories in the file browser"
793      )
794    , ( FB.fileBrowserBlockDeviceAttr
795      , "Attribute for block devices in the file browser"
796      )
797    , ( FB.fileBrowserRegularFileAttr
798      , "Attribute for regular files in the file browser"
799      )
800    , ( FB.fileBrowserCharacterDeviceAttr
801      , "Attribute for character devices in the file browser"
802      )
803    , ( FB.fileBrowserNamedPipeAttr
804      , "Attribute for named pipes in the file browser"
805      )
806    , ( FB.fileBrowserSymbolicLinkAttr
807      , "Attribute for symbolic links in the file browser"
808      )
809    , ( FB.fileBrowserUnixSocketAttr
810      , "Attribute for Unix sockets in the file browser"
811      )
812    , ( buttonAttr
813      , "Attribute for input form buttons"
814      )
815    , ( buttonFocusedAttr
816      , "Attribute for focused input form buttons"
817      )
818    , ( currentUserAttr
819      , "Attribute for the username of the user running Matterhorn"
820      )
821    , ( currentTeamAttr
822      , "The currently-selected team"
823      )
824    ] <> [ (usernameAttr i, T.pack $ "Username color " <> show i)
825         | i <- [0..usernameColorHashBuckets-1]
826         ]
827