packages feed

zwirn-0.2.3.1: app/zwirnmill/Editor/Draw.hs

module Editor.Draw where

import Brick
import qualified Brick.Widgets.Border as Border
import Control.Monad ((<=<))
import Data.List (find, groupBy)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Zipper as Z
import Editor.Core
import Editor.Diagnostic
import Editor.Selection
import Editor.Util
import Lens.Micro
import UI.Attributes
import UI.Core
import Zwirn.Language.Lexer (Lexeme, Token (..), tokenise)
import Zwirn.Language.Location (Located (..), RealSrcLoc (..), SrcLoc (..))

drawEditor :: Bool -> (Name, (Int, Int), EditorState) -> Widget Name
drawEditor active (name, size, es) =
  vBox
    [ drawEditorBody active name size es,
      vLimit 1 $ drawStatusBar es
    ]

drawStatusBar :: EditorState -> Widget Name
drawStatusBar es =
  let filePart = fromMaybe "[No Name]" (es ^. esFilePath)
      unsaved = if es ^. esUnsaved then "*" else " "
      cursor = Z.cursorPosition $ es ^. esZipper
      pos =
        "Ln "
          ++ show (fst cursor + 1)
          ++ ", Col "
          ++ show (snd cursor + 1)
      msg = fromMaybe "" (es ^. esMessage)
      left = " " ++ unsaved ++ " " ++ filePart ++ "  " ++ msg
      right = pos ++ " "
   in withAttr attrStatusBar $
        hBox
          [ str left,
            fill ' ',
            str right
          ]

drawPopup :: Maybe Popup -> Widget Name
drawPopup Nothing = emptyWidget
drawPopup (Just (Popup content (col, row))) = translateBy (Location (col + 1, row + 1)) $ Border.border $ str content

drawEditorBody :: Bool -> Name -> (Int, Int) -> EditorState -> Widget Name
drawEditorBody active name (sx, sy) es = hBox [lineNumbers, viewport (fromName name) Both $ visibleRegion (Location (c, r)) (1, 1) $ cursor $ vLimit (max cy sy) $ hLimit (max cx sx) $ vBox (rowWidgets ++ bottomPadding)]
  where
    fromName (Editor i) = EditorViewport i
    fromName x = x
    cy = lineCount es
    cx = getLongestLine es

    ds = es ^. esDiagnostics
    flashed i = case es ^. esFlashBlock of
      Nothing -> Nothing
      Just (ty, (st, en)) -> if st <= i && i <= en then Just ty else Nothing
    (r, c) = currentCursor es

    maxLineWidth = lineNumberWidth es
    allLines = getLines es

    indexedRows = zipWith (\ro l -> (ro, l, rowDiagnostics ro ds, flashed ro)) [0 ..] allLines
    lineNumbers = vBox $ map (drawLineNumber (fromName name) r maxLineWidth) [0 .. length allLines - 1]

    rowWidgets = map (drawRow es) indexedRows

    remainingRows = max 0 (sy - cy)
    bottomPadding = replicate remainingRows (fill ' ')
    cursor = if active then showCursor Cursor (Location (c, r)) else id

drawRow :: EditorState -> (Int, Text, [Diagnostic], Maybe OutputType) -> Widget Name
drawRow es (row, rawLine, ds, flashed) =
  let cursor = currentCursor es
      isCurRow = fst cursor == row
      tokens = tokenizeLine (es ^. esSelection) cursor row ds rawLine
      lineWidget = hBox [renderTokens tokens, fill ' ']
      rowWidget = lineWidget
   in case flashed of
        Just OutputInfo -> withDefAttr attrFlash rowWidget
        Just OutputError -> withDefAttr attrFlashError rowWidget
        _ ->
          if isCurRow
            then withDefAttr attrCurrentLine rowWidget
            else rowWidget

cellAttr :: Maybe (Int, Int) -> (Int, Int) -> Int -> Int -> [Diagnostic] -> Maybe AttrName -> Maybe AttrName
cellAttr Nothing _ row col ds@(_ : _) x = if inAnyDiagnostic (row, col) ds then Just attrError else x
cellAttr Nothing _ _ _ _ x = x
cellAttr (Just sel) cursor row col ds matt
  | insel && inerr = Just $ attrSelected <> attrError
  | insel = maybe (Just attrSelected) (\att -> Just $ attrSelected <> att) matt
  | inerr = Just attrError
  | otherwise = matt
  where
    insel = inSelection (row, col) cursor (Just sel)
    inerr = inAnyDiagnostic (row, col) ds

tokenizeLine :: Maybe (Int, Int) -> (Int, Int) -> Int -> [Diagnostic] -> Text -> [(Maybe AttrName, Text)]
tokenizeLine mSel cursor row ds text =
  concatMap toToken $
    groupBy sameAttr $
      zipWith (\col ch -> (cellAttr mSel cursor row col ds (inLexeme col ls), ch)) [0 ..] (T.unpack text)
  where
    toToken [] = []
    toToken ((a, x) : pairs) = [(a, T.pack $ x : map snd pairs)]
    sameAttr (a, _) (b, _) = a == b
    ls = getLexemes text

renderTokens :: [(Maybe AttrName, Text)] -> Widget Name
renderTokens ts = hBox [maybe (txt s) (\a -> withAttr a (txt s)) attr | (attr, s) <- ts]

drawLineNumber :: Name -> Int -> Int -> Int -> Widget Name
drawLineNumber vpn currentRow maxWidth row = Widget Fixed Fixed $ do
  vp <- unsafeLookupViewport vpn
  let ro = maybe 0 (\(VP _ x _ _) -> x) vp
      line = show (row + ro + 1) ++ " "
      padding = maxWidth - length line
      attr = if currentRow == row + ro then withAttr attrCurrentLineNum else withAttr attrLineNum
  render $ attr $ padLeft (Pad padding) $ str line

getLexemes :: Text -> [Lexeme]
getLexemes t = case tokenise t of
  Left _ -> []
  Right ls -> ls

inLexeme :: Int -> [Lexeme] -> Maybe AttrName
inLexeme i = strip <=< find (\(Located p _) -> contained p)
  where
    contained (SrcLoc (RealSrcLoc _ _ sc _ ec)) = sc - 1 <= i && i < ec - 1
    contained _ = False
    strip (Located _ x) = tokenToAttribute x

tokenToAttribute :: Token -> Maybe AttrName
tokenToAttribute (TextTok _) = Just syntaxText
tokenToAttribute (NumberTok _) = Just syntaxNumber
tokenToAttribute (OperatorTok _) = Just syntaxOperator
tokenToAttribute (SpecialOperatorTok _) = Just syntaxOperator
tokenToAttribute RestTok = Just syntaxSilence
tokenToAttribute IfTok = Just syntaxKeyword
tokenToAttribute ThenTok = Just syntaxKeyword
tokenToAttribute ElseTok = Just syntaxKeyword
tokenToAttribute LambdaTok = Just syntaxKeyword
tokenToAttribute ArrowTok = Just syntaxKeyword
tokenToAttribute DefineTok = Just syntaxKeyword
tokenToAttribute DynamicDefineTok = Just syntaxKeyword
tokenToAttribute TypeCommandTok = Just syntaxKeyword
tokenToAttribute ShowCommandTok = Just syntaxKeyword
tokenToAttribute InfoCommandTok = Just syntaxKeyword
tokenToAttribute ResetShowConfigCommandTok = Just syntaxKeyword
tokenToAttribute ResetEnvCommandTok = Just syntaxKeyword
tokenToAttribute SetCommandTok = Just syntaxKeyword
tokenToAttribute StatusCommandTok = Just syntaxKeyword
tokenToAttribute EnvCommandTok = Just syntaxKeyword
tokenToAttribute (LoadCommandTok _) = Just syntaxKeyword
tokenToAttribute _ = Nothing