module Yi.Mode.JavaScript (javaScriptMode, hooks) where
import Control.Monad.Writer.Lazy (execWriter)
import Data.List (nub)
import Data.Maybe (isJust)
import Data.Typeable (Typeable)
import Prelude (unlines, map)
import System.FilePath.Posix (takeBaseName)
import Yi.Buffer.Basic (BufferRef, Direction(..), fromString)
import Yi.Buffer.Indent (indentSettingsB, indentOfB, cycleIndentsB, newlineAndIndentB)
import Yi.Buffer.HighLevel (replaceBufferContent, getNextNonBlankLineB, moveToSol)
import Yi.Buffer.Misc (Mode(..), BufferM, IndentBehaviour, file, pointAt, shiftWidth)
import Yi.Core ( (.), Mode, emptyMode, modeApplies, modeName
, modeToggleCommentSelection, toggleCommentSelectionB, modeHL
, Char, modeGetStrokes, ($), withSyntax )
import Yi.Dynamic (Initializable)
import Yi.Editor (withEditor, withOtherWindow, getDynamic, stringToNewBuffer
, findBuffer, switchToBufferE)
import Yi.Event (Key(..), Event(..))
import Yi.File (fwriteE)
import Yi.IncrementalParse (scanner)
import Yi.Interact (choice)
import Yi.Keymap (YiM, Action(..), withBuffer, withGivenBuffer, topKeymapA)
import Yi.Keymap.Keys (ctrlCh, (?>>), (?>>!), (<||))
import Yi.Lexer.Alex (AlexState, Tok, lexScanner)
import Yi.Lexer.JavaScript (alexScanToken, TT, initState, HlState, Token)
import Yi.Modes (anyExtension)
import Yi.Prelude hiding (list)
import Yi.Syntax (ExtHL(..), mkHighlighter, Scanner, Point)
import Yi.Syntax.JavaScript (Tree, parse, getStrokes)
import Yi.Syntax.Tree (getLastPath)
import Yi.Verifier.JavaScript (verify)
import qualified Data.DList as D
javaScriptAbstract :: Mode syntax
javaScriptAbstract = emptyMode
{
modeApplies = anyExtension ["js"]
, modeName = "javascript"
, modeToggleCommentSelection = toggleCommentSelectionB "// " "//"
}
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 (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 :: Scanner Point Char -> Scanner (AlexState HlState) (Tok Token)
jsLexer = lexScanner alexScanToken initState
hooks :: Mode (Tree TT) -> Mode (Tree TT)
hooks mode = mode
{
modeKeymap = topKeymapA ^: (choice [ctrlCh 'c' ?>> ctrlCh 'l' ?>>! withSyntax modeFollow,
Event KEnter [] ?>>! newlineAndIndentB]
<||)
, modeFollow = YiA . jsCompile
}
newtype JSBuffer = JSBuffer (Maybe BufferRef)
deriving (Initializable, Typeable)
jsCompile :: Tree TT -> YiM ()
jsCompile tree = do
fwriteE
Just filename <- withBuffer $ 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 $ getDynamic
case mb of
Nothing -> mkJSBuffer
Just b -> do stillExists <- withEditor $ isJust <$> findBuffer b
if stillExists
then return b
else mkJSBuffer
mkJSBuffer :: YiM BufferRef
mkJSBuffer = withEditor $ stringToNewBuffer (Left "js") (fromString "")
jsErrors :: Show a => String -> BufferRef -> [a] -> YiM ()
jsErrors fname buf errs =
let problems = unlines $ map item errs
item x = ("* " ++ show x)
str = if null errs
then "No problems found!"
else "Problems in " ++ takeBaseName fname ++ ":\n" ++ problems
in withGivenBuffer buf (replaceBufferContent str)