mirror of
https://github.com/jtdaugherty/brick.git
synced 2024-10-27 01:27:24 +03:00
136 lines
4.6 KiB
Haskell
136 lines
4.6 KiB
Haskell
{-# LANGUAGE TemplateHaskell #-}
|
|
{-# LANGUAGE CPP #-}
|
|
module Main where
|
|
|
|
import Control.Monad (void)
|
|
import Lens.Micro
|
|
import Lens.Micro.TH
|
|
import Lens.Micro.Mtl
|
|
import qualified Graphics.Vty as V
|
|
#if !(MIN_VERSION_base(4,11,0))
|
|
import Data.Semigroup ((<>))
|
|
#endif
|
|
|
|
import qualified Brick.Main as M
|
|
import qualified Brick.Types as T
|
|
import Brick.Widgets.Core
|
|
( (<+>)
|
|
, vBox
|
|
, hLimit
|
|
, vLimit
|
|
, str
|
|
, visible
|
|
, viewport
|
|
, withDefAttr
|
|
)
|
|
import qualified Brick.Widgets.Center as C
|
|
import qualified Brick.Widgets.Edit as E
|
|
import qualified Brick.AttrMap as A
|
|
import Brick.Util (on, fg)
|
|
|
|
data Name = Edit
|
|
| EditLines
|
|
deriving (Ord, Show, Eq)
|
|
|
|
data St =
|
|
St { _edit :: E.Editor String Name
|
|
}
|
|
|
|
makeLenses ''St
|
|
|
|
drawUI :: St -> [T.Widget Name]
|
|
drawUI st = [ui]
|
|
where
|
|
e = renderWithLineNumbers (st^.edit)
|
|
ui = C.center $ hLimit 50 $ vLimit 10 e
|
|
|
|
-- | Given an editor, render the editor with line numbers to the left of
|
|
-- the editor.
|
|
--
|
|
-- This essentially exploits knowledge of how the editor is implemented:
|
|
-- we make a viewport containing line numbers that is just as high as
|
|
-- the editor, then request that the line number associated with the
|
|
-- editor's current line position be made visible, thus scrolling it
|
|
-- into view. This is slightly brittle, however, because it relies on
|
|
-- essentially keeping the line number viewport and the editor viewport
|
|
-- in the same vertical scrolling state; with direct scrolling requests
|
|
-- from EventM it is easily possible to put the two viewports into a
|
|
-- state where they do not have the same vertical scrolling offset. That
|
|
-- means that visibility requests made with 'visible' won't necessarily
|
|
-- have the same effect in each viewport in that case. So this is
|
|
-- only really usable in the case where you're sure that the editor's
|
|
-- viewport and the line number viewports will not be managed by direct
|
|
-- viewport operations in EventM. That's what I'd recommend anyway, but
|
|
-- still, this is an important caveat.
|
|
--
|
|
-- There's another important caveat here: this particular implementation
|
|
-- has @O(n)@ performance for editor height @n@ because we generate
|
|
-- the entire list of line numbers on each rendering depending on the
|
|
-- height of the editor. That means that for sufficiently large files,
|
|
-- it will get more expensive to render the line numbers. There is a way
|
|
-- around this problem, which is to take the approach that the @List@
|
|
-- implementation takes: only render a region of visible line numbers
|
|
-- around the currently-edited line that is just large enough to be
|
|
-- guaranteed to fill the viewport, then translate that so that it
|
|
-- appears at the right viewport offset, thus faking a viewport filled
|
|
-- with line numbers when in fact we'd only ever render at most @2 * K +
|
|
-- 1@ line numbers for a viewport height of @K@. That's more involved,
|
|
-- so I didn't do it here, but that would be the way to go for a Real
|
|
-- Application.
|
|
renderWithLineNumbers :: E.Editor String Name -> T.Widget Name
|
|
renderWithLineNumbers e =
|
|
lineNumbersVp <+> editorVp
|
|
where
|
|
lineNumbersVp = hLimit (maxNumWidth + 1) $ viewport EditLines T.Vertical body
|
|
editorVp = E.renderEditor (str . unlines) True e
|
|
body = withDefAttr lineNumberAttr $ vBox numWidgets
|
|
numWidgets = mkNumWidget <$> numbers
|
|
mkNumWidget i = maybeVisible i $ str $ show i
|
|
maybeVisible i
|
|
| i == curLine + 1 =
|
|
visible . withDefAttr currentLineNumberAttr
|
|
| otherwise =
|
|
id
|
|
numbers = [1..h]
|
|
contents = E.getEditContents e
|
|
h = length contents
|
|
curLine = fst $ E.getCursorPosition e
|
|
maxNumWidth = length $ show h
|
|
|
|
appEvent :: T.BrickEvent Name e -> T.EventM Name St ()
|
|
appEvent (T.VtyEvent (V.EvKey V.KEsc [])) =
|
|
M.halt
|
|
appEvent ev = do
|
|
zoom edit $ E.handleEditorEvent ev
|
|
|
|
initialState :: St
|
|
initialState =
|
|
St (E.editor Edit Nothing "")
|
|
|
|
lineNumberAttr :: A.AttrName
|
|
lineNumberAttr = A.attrName "lineNumber"
|
|
|
|
currentLineNumberAttr :: A.AttrName
|
|
currentLineNumberAttr = lineNumberAttr <> A.attrName "current"
|
|
|
|
theMap :: A.AttrMap
|
|
theMap = A.attrMap V.defAttr
|
|
[ (E.editAttr, V.white `on` V.blue)
|
|
, (E.editFocusedAttr, V.black `on` V.yellow)
|
|
, (lineNumberAttr, fg V.cyan)
|
|
, (currentLineNumberAttr, V.defAttr `V.withStyle` V.bold)
|
|
]
|
|
|
|
theApp :: M.App St e Name
|
|
theApp =
|
|
M.App { M.appDraw = drawUI
|
|
, M.appChooseCursor = const $ M.showCursorNamed Edit
|
|
, M.appHandleEvent = appEvent
|
|
, M.appStartEvent = return ()
|
|
, M.appAttrMap = const theMap
|
|
}
|
|
|
|
main :: IO ()
|
|
main = do
|
|
void $ M.defaultMain theApp initialState
|