module Yi.Mode.JavaScript (javaScriptMode, hooks) where
import Control.Applicative
import Control.Lens
import Control.Monad.Writer.Lazy (execWriter)
import Data.Binary
import Data.DList as D (toList)
import Data.Default
import Data.Foldable as F (toList)
import Data.List (nub)
import Data.Maybe (isJust)
import Data.Monoid
import qualified Data.Text as T
import Data.Typeable
import System.FilePath.Posix (takeBaseName)
import Yi.Buffer
import Yi.Core (withSyntax)
import Yi.Types (YiVariable)
import Yi.Editor (withEditor, withOtherWindow, getEditorDyn,
stringToNewBuffer , findBuffer, switchToBufferE,
withCurrentBuffer, withGivenBuffer)
import Yi.Event (Key(..), Event(..))
import Yi.File (fwriteE)
import Yi.IncrementalParse (scanner)
import Yi.Interact (choice)
import Yi.Keymap (YiM, Action(..), topKeymapA)
import Yi.Keymap.Keys (ctrlCh, (?>>), (?>>!), important)
import Yi.Lexer.Alex (AlexState, Tok, lexScanner,
commonLexer, CharScanner)
import Yi.Lexer.JavaScript (alexScanToken, TT, initState,
HlState, Token)
import Yi.Modes (anyExtension)
import Yi.Monad
import qualified Yi.Rope as R
import Yi.String
import Yi.Syntax (ExtHL(..), mkHighlighter, Scanner)
import Yi.Syntax.JavaScript (Tree, parse, getStrokes)
import Yi.Syntax.Tree (getLastPath)
import Yi.Verifier.JavaScript (verify)
javaScriptAbstract :: Mode syntax
javaScriptAbstract = emptyMode
{ modeApplies = anyExtension ["js"]
, modeName = "javascript"
, modeToggleCommentSelection = Just (toggleCommentB "//")
}
javaScriptMode :: Mode (Tree TT)
javaScriptMode = javaScriptAbstract
{ modeIndent = jsSimpleIndent
, modeHL = ExtHL $ mkHighlighter (scanner parse . jsLexer)
, modeGetStrokes = getStrokes
}
jsSimpleIndent :: Tree TT -> IndentBehaviour -> BufferM ()
jsSimpleIndent t behave = do
indLevel <- shiftWidth <$> indentSettingsB
prevInd <- getNextNonBlankLineB Backward >>= indentOfB
solPnt <- pointAt moveToSol
let path = getLastPath (F.toList t) solPnt
case path of
Nothing -> indentTo [indLevel, 0]
Just _ -> indentTo [prevInd,
prevInd + indLevel,
prevInd indLevel]
where
indentTo :: [Int] -> BufferM ()
indentTo = cycleIndentsB behave . nub
jsLexer :: CharScanner -> Scanner (AlexState HlState) (Tok Token)
jsLexer = lexScanner (commonLexer alexScanToken initState)
hooks :: Mode (Tree TT) -> Mode (Tree TT)
hooks mode = mode
{ modeKeymap = topKeymapA %~ important (choice m)
, modeFollow = YiA . jsCompile
}
where
m = [ ctrlCh 'c' ?>> ctrlCh 'l' ?>>! withSyntax modeFollow
, Event KEnter [] ?>>! newlineAndIndentB
]
newtype JSBuffer = JSBuffer (Maybe BufferRef)
deriving (Default, Typeable, Binary)
instance YiVariable JSBuffer
jsCompile :: Tree TT -> YiM ()
jsCompile tree = do
fwriteE
Just filename <- withCurrentBuffer $ gets file
buf <- getJSBuffer
withOtherWindow $ withEditor $ switchToBufferE buf
jsErrors filename buf (D.toList $ execWriter $ verify tree)
getJSBuffer :: YiM BufferRef
getJSBuffer = withOtherWindow $ do
JSBuffer mb <- withEditor getEditorDyn
case mb of
Nothing -> mkJSBuffer
Just b -> do stillExists <- isJust <$> findBuffer b
if stillExists
then return b
else mkJSBuffer
mkJSBuffer :: YiM BufferRef
mkJSBuffer = stringToNewBuffer (MemBuffer "js") mempty
jsErrors :: Show a => String -> BufferRef -> [a] -> YiM ()
jsErrors fname buf errs =
let problems = T.unlines $ map item errs
item x = "* " <> showT x
str = if null errs
then "No problems found!"
else "Problems in "
<> R.fromString (takeBaseName fname)
<> ":\n" <> R.fromText problems
in withGivenBuffer buf (replaceBufferContent str)