diff options
| -rw-r--r-- | jterm.hs | 76 | 
1 files changed, 41 insertions, 35 deletions
| @@ -1,6 +1,7 @@ -{-# LANGUAGE OverloadedStrings #-} + {-# LANGUAGE OverloadedStrings #-}  {-# LANGUAGE ScopedTypeVariables #-}  {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE StrictData #-}  import Control.Concurrent  import Control.Exception @@ -10,6 +11,7 @@ import Control.Monad.Trans.State  import Data.Bits  import qualified Data.ByteString.UTF8 as B  import Data.Function +import Data.Functor  import qualified Data.Map as M  import Data.Maybe  import Data.String.AnsiEscapeCodes.Strip.Text @@ -18,7 +20,6 @@ import qualified Data.Text as T  import qualified Data.Text.IO as T  import Data.Text.Rope (Position (..), Rope)  import qualified Data.Text.Rope as R -import Debug.Trace  import Foreign.C.String  import Foreign.C.Types  import GHC.IO.Encoding @@ -38,6 +39,7 @@ doubleClickDelay = 300  a -. b    | b > a = 0    | otherwise = a - b +  infixl 1 -.  memo f = unsafePerformIO $ do @@ -55,6 +57,7 @@ data Buffer = Buffer    { pos :: Word,      content :: Rope,      cursor :: (Word, Word), +    ptycursor :: Word,      selection :: Maybe (CInt, CInt),      pty :: Pty,      lastLeftClick :: Time @@ -341,13 +344,13 @@ handleEvent display win bgcolour fgcolour linecolour selcolour font event = do                case (mod, keysym) of                  (4, 117) ->                    -- ctrl-u (delete content before cursor) -                  put $ b {content = R.splitAt start (content b) & snd, cursor = (0, end-start), pos = 0} +                  delins' 0 start ""                  (4, 105) ->                    -- ctrl-i (delete content after cursor) -                  put $ b {content = R.splitAt end (content b) & fst} +                  delins' end (R.length (content b)) ""                  (4, 108) ->                    -- ctrl-l (restrip buffer of ansi codes) -                  put $ b {content = content b & R.toText & stripAnsiEscapeCodes & R.fromText} +                  delins' 0 (R.length (content b)) (content b & R.toText & stripAnsiEscapeCodes & R.fromText)                  (4, 99) -> lift $ do                    -- ctrl-c (copy)                    storeBuffer display (R.toText selstr & T.unpack) 0 @@ -356,7 +359,7 @@ handleEvent display win bgcolour fgcolour linecolour selcolour font event = do                  (4, 120) -> do                    -- ctrl-x (cut)                    lift $ storeBuffer display (R.toText selstr & T.unpack) 0 -                  put $ b {content = delins start end (content b) "", cursor = (start + 1, start + 1)} +                  delins' start end ""                  (4, 118) -> lift $ do                    -- ctrl-v (paste)                    prop <- internAtom display "JTERM_CLIPBOARD" True @@ -367,7 +370,8 @@ handleEvent display win bgcolour fgcolour linecolour selcolour font event = do                    T.writeFile (home <> "/jterm.log") (R.toText (content b))                  (4, 115) -> do                    -- ctrl-s (send) -                  lift $ send b (selstr <> "\n") +                  put $ b {content = content b <> selstr <> "\n"} +                  sendline                  (_, 65361) -> do                    -- left                    let s = start - min start 1 @@ -397,40 +401,24 @@ handleEvent display win bgcolour fgcolour linecolour selcolour font event = do                    scrolldown ((681 * pageheight) `div` 1000)                  (_, 65288) -> do                    -- backspace -                  let s = start -. 1 -                  put $ b {content = delins s end (content b) "", cursor = (s, s)} -                  when (start == R.length (content b)) $ lift $ sendCommand b Erase +                  delins' (start - 1) end ""                  _ -> when (toAppend /= "") $ do -                  if start == R.length (content b) -                    then lift $ send b toAppend -                    else put $ b {content = delins start end (content b) toAppend, cursor = (start + 1, start + 1)} +                  delins' start end toAppend +                  when (toAppend == "\n" && start >= ptycursor b) $ sendline        redraw      T toAppend -> do -      if toAppend == "\b \b" -        then -- Erase character - -          let (c, _) = R.splitAt l (content b) -              l = R.length (content b) -              p = min start l -           in put $ b {content = c, cursor = (p, p)} -        else do -          let c = content b <> R.fromText expandedTabs -              pos = R.length c -              noesc = stripAnsiEscapeCodes toAppend -              expandedTabs = T.intercalate "\n" $ map expandTabs $ T.splitOn "\n" noesc -          put $ b {content = c} -          when (start == R.length (content b)) $ modify $ \b -> b {cursor = (pos, pos)} +      let c = content b <> R.fromText expandedTabs +          pos = R.length c +          noesc = stripAnsiEscapeCodes toAppend +          expandedTabs = T.intercalate "\n" $ map expandTabs $ T.splitOn "\n" noesc +      put $ b {content = c, ptycursor = pos} +      when (start == R.length (content b)) $ modify $ \b -> b {cursor = (pos, pos)}        redraw      Notify -> do        prop <- lift $ internAtom display "JTERM_CLIPBOARD" False        Just (R.fromText . T.pack . map castCCharToChar -> str) <- lift $ getWindowProperty8 display prop win -      if start == R.length (content b) -        then lift $ send b str -        else do -          let s = start + n -              n = fromIntegral (R.length str) -          put $ b {content = delins start end (content b) str, cursor = (s, s)} -          redraw +      delins' start end str +      redraw      Expose -> redraw      Selection e -> lift $ allocaXEvent $ \ev -> do        setEventType ev selectionNotify @@ -446,6 +434,11 @@ handleEvent display win bgcolour fgcolour linecolour selcolour font event = do    where      xUtf8String = 315 :: Atom +    sendline = do +      b <- get +      let cmd = between (ptycursor b) (R.length $ content b) (content b) +      lift $ send b $ cmd +      sendCommand b c = do        attrs <- getTerminalAttributes (pty b)        writePty (pty b) (B.fromString [fromJust $ controlChar attrs c]) @@ -454,6 +447,15 @@ handleEvent display win bgcolour fgcolour linecolour selcolour font event = do        drawWin display win bgcolour fgcolour linecolour selcolour font        lift $ sync display False +    between s e rp = R.splitAt e rp & fst & R.splitAt s & snd +     +    delins' s e ins = do +      b <- get +      let l = R.length ins +          delta = p + l + s - e +          p = ptycursor b +      put $ b {content = delins s e (content b) ins, cursor = (s + l, s + l), ptycursor = if e < p then delta else if s < p then s else p} +      delins :: Word -> Word -> Rope -> Rope -> Rope      delins s e rp ins =        let (before, _) = R.splitAt s rp @@ -498,6 +500,10 @@ main = do    env <- getEnvironment    let shell = case filter (\(var, _) -> var == "SHELL") env of [(_, x)] -> x; _ -> "sh"    (pty, _) <- spawnWithPty (Just $ env ++ [("TERM", "dumb")]) True shell [] (80, 25) +  getTerminalAttributes pty +    <&> flip withoutMode EnableEcho +    <&> flip withMode MapCRtoLF +    >>= flip (setTerminalAttributes pty) Immediately    void $ bracket (openDisplay "") closeDisplay $ \display -> do      let screenno = defaultScreen display @@ -520,5 +526,5 @@ main = do            withXftColorValue display visual colourmap (XRenderColor 0xaeae 0xf7f7 0xffff 0xffff) $ \linecolour -> do              selectInput display win (exposureMask .|. buttonPressMask .|. buttonReleaseMask .|. keyPressMask .|. pointerMotionMask)              allocaXEvent $ \e -> -              flip runStateT (Buffer 0 "" (0, 0) Nothing pty 0) $ +              flip runStateT (Buffer 0 "" (0, 0) 0 Nothing pty 0) $                  S.parList id [S.repeatM (lift $ watch pty), S.repeatM (lift $ waitEvent display e)] & S.fold (F.drainMapM (handleEvent display win bgcolour fgcolour linecolour selcolour font)) | 
