@@ -9,6 +9,7 @@ module Lamdera.CLI.Live where
99import qualified Data.ByteString.Builder as B
1010import qualified Data.ByteString as BS
1111import qualified Data.ByteString.Lazy as BSL
12+ import qualified Data.ByteString.Char8
1213import qualified Data.Text as T
1314import qualified Data.Text.Lazy as TL
1415import qualified Data.Text.Encoding as TE
@@ -20,11 +21,12 @@ import Data.Maybe (fromMaybe)
2021import GHC.Word (Word64 )
2122
2223import qualified System.Directory as Dir
23- import System.FilePath ((</>) , takeExtension )
24+ import qualified System.FilePath as FP
25+ import System.FilePath ((</>) )
2426import Control.Applicative ((<|>) )
2527import Control.Arrow ((***) )
2628import Control.Concurrent.STM (atomically , newTVarIO , readTVar , readTVarIO , writeTVar , TVar )
27- import Control.Exception (finally )
29+ import Control.Exception (finally , try , SomeException )
2830import qualified Language.Haskell.TH as TH
2931import Data.FileEmbed (bsToExp )
3032import qualified Data.Aeson.Encoding as A
@@ -49,12 +51,13 @@ import System.Entropy (getEntropy)
4951import Snap.Util.FileServe (
5052 getSafePath , serveDirectoryWith , defaultDirectoryConfig , defaultMimeTypes , mimeTypes , DirectoryConfig
5153 )
52- import Control.Monad (guard )
54+ import Control.Monad (guard , mfilter )
5355
5456import qualified Lamdera.CLI.Check
5557import qualified Lamdera.Relative
5658import qualified Lamdera.Version
5759import qualified Ext.Common
60+ import qualified GHC.IO.Exception
5861
5962
6063
@@ -119,7 +122,7 @@ directoryConfig =
119122serveUnmatchedUrlsToIndex :: FilePath -> (FilePath -> Snap () ) -> Snap ()
120123serveUnmatchedUrlsToIndex root serveElm =
121124 do file <- getSafePath
122- guard (takeExtension file == " " )
125+ guard (FP. takeExtension file == " " )
123126 serveElm (lamderaCache root </> " Lamdera" </> " Live.elm" )
124127
125128
@@ -309,26 +312,24 @@ serveWebsocket root (mClients, mLeader, mChan, beState) =
309312
310313openEditorHandler :: FilePath -> Snap ()
311314openEditorHandler root = do
312- fullpath <- T. pack <$> getSafePath
313- let
314- handlers =
315- -- *nix dir paths
316- [ (" _x/editor" , serveEditorOpen root)
317- -- Windows dir paths
318- , (" _x\\ editor" , serveEditorOpen root)
319- ]
320- handlers
321- & List. find (\ (prefix, handler) ->
322- prefix `T.isPrefixOf` fullpath
323- )
324- & fmap (\ (prefix, handler) -> do
325- let path =
326- fullpath & T. replace (prefix <> " /" ) " " -- Strip when sub-dirs
327- & T. replace (prefix <> " \\ " ) " " -- Strip when sub-dirs windows
328- & T. replace prefix " " -- Strip when root dir
329- handler path
330- )
331- & withDefault pass
315+ fullpath <- getSafePath
316+ debug $ " _x/editor fullpath: " ++ fullpath
317+ case FP. splitDirectories fullpath of
318+ " _x" : " editor" : rest -> do
319+ maybeRow <- getQueryParam " row"
320+ maybeColumn <- getQueryParam " column"
321+
322+ let parseNonNegative = mfilter (>= 0 ) . readMaybe . Data.ByteString.Char8. unpack
323+
324+ case (maybeRow >>= parseNonNegative, maybeColumn >>= parseNonNegative) of
325+ (Just row, Just column) ->
326+ serveEditorOpen root (FP. joinPath rest) row column
327+
328+ _ ->
329+ error400PlainText " Unexpected request, expecting format: /_x/editor/<filename>?row=<row>&column=<column>"
330+
331+ _ ->
332+ pass
332333
333334
334335serveBem :: LiveState -> Snap ()
@@ -431,80 +432,83 @@ serveExperimentalList root path = do
431432 error404 " folder not found"
432433
433434
434- serveEditorOpen :: FilePath -> Text -> Snap ()
435- serveEditorOpen root path = do
435+ serveEditorOpen :: FilePath -> FilePath -> Int -> Int -> Snap ()
436+ serveEditorOpen root path row column = do
436437 debug $ " _x/editor received: " ++ show path
437- case path & T. splitOn " :" of
438- file: row: column: xs -> do
439- let fullpath = (root </> T. unpack file)
440- debug $ " _x/editor: " ++ show fullpath
441- exists_ <- liftIO $ Dir. doesFileExist fullpath
442- if exists_
443- then do
444- tryOpenInDetectedEditor root fullpath row column
445-
446- else do
447- error404 " file not found"
448- _ ->
449- error404 " unexpected identifier, expecting format: <filename>:<row>:<column>"
438+ let fullpath = root </> path
439+ debug $ " _x/editor: " ++ show fullpath
440+ exists_ <- liftIO $ Dir. doesFileExist fullpath
441+ if exists_
442+ then do
443+ tryOpenInDetectedEditor root fullpath row column
444+
445+ else do
446+ error400PlainText " File not found"
450447
451448
452- tryOpenInDetectedEditor :: FilePath -> FilePath -> Text -> Text -> Snap ()
449+ tryOpenInDetectedEditor :: FilePath -> FilePath -> Int -> Int -> Snap ()
453450tryOpenInDetectedEditor root file row column = do
454- res <- liftIO $ mapM id (editors root)
451+ res <- liftIO $ sequence (editors root)
455452 case justs res of
456453 [] ->
457- -- @TODO give more helpful error that guides user how to configure things?
458- error404 " No supported editors found"
454+ error404 " No supported editors found. See the Lamdera docs for more information."
459455
460- (editor, openEditor): xs -> do
456+ (editor, openEditor): _ -> do
461457 debug " 📝 found the following editors, opening first:"
462458 justs res & fmap fst & show & debug
463459
464- liftIO $ openEditor file row column
465- jsonResponse $ " { status: 'tried opening editor " <> editor <> " ' }"
460+ runRes <- liftIO (try (openEditor file row column) :: IO (Either SomeException (GHC.IO.Exception. ExitCode , String , String )))
461+ case runRes of
462+ Right (exit, stdout, stderr) ->
463+ case exit of
464+ GHC.IO.Exception. ExitSuccess ->
465+ noContentResponse
466+
467+ GHC.IO.Exception. ExitFailure exitCode ->
468+ error400PlainText $ Ext.Common. stringToBuilder $ " exit " <> show exitCode <> " : " <> stdout <> stderr
469+
470+ Left err ->
471+ error400PlainText $ Ext.Common. stringToBuilder $ show err
466472
467473
468- type EditorOpenIO = (FilePath -> Text -> Text -> IO String )
474+ type EditorOpenIO = (FilePath -> Int -> Int -> IO ( GHC.IO.Exception. ExitCode , String , String ) )
469475
470476
471477editors :: FilePath -> [IO (Maybe (B. Builder , EditorOpenIO ))]
472478editors projectRoot =
473479 [ detectEditor " custom-*nix"
474480 (Dir. doesFileExist (projectRoot </> " openEditor.sh" ))
475- (\ file row column -> Ext.Common. execCombineStdOutErr (projectRoot </> " openEditor.sh" ) [file, T. unpack row, T. unpack column] " " )
481+ (\ file row column -> Ext.Common. c_ (projectRoot </> " openEditor.sh" ) [file, show row, show column] " " )
476482
477483 , detectEditor " custom-windows"
478484 (do
479485 exists <- Dir. doesFileExist (projectRoot </> " openEditor.bat" )
480486 pure $ exists && ostype == Windows
481487 )
482- (\ file row column -> Ext.Common. execCombineStdOutErr (projectRoot </> " openEditor.bat" ) [file, T. unpack row, T. unpack column] " " )
488+ (\ file row column -> Ext.Common. c_ (projectRoot </> " openEditor.bat" ) [file, show row, show column] " " )
483489
484490 , detectExecutable " code-insiders"
485491 (\ executablePath file row column -> do
486- Ext.Common. execCombineStdOutErr executablePath [ " -g" , file <> " :" <> T. unpack row <> " :" <> T. unpack column] " "
492+ Ext.Common. c_ executablePath [ " -g" , file <> " :" <> show row <> " :" <> show column] " "
487493 )
488494
489495 , detectExecutable " code"
490496 (\ executablePath file row column -> do
491- Ext.Common. execCombineStdOutErr executablePath [ " -g" , file <> " :" <> T. unpack row <> " :" <> T. unpack column] " "
497+ Ext.Common. c_ executablePath [ " -g" , file <> " :" <> show row <> " :" <> show column] " "
492498 )
493499
494500 , detectEditor " intellij-ce"
495501 (Dir. doesDirectoryExist " /Applications/IntelliJ IDEA CE.app" )
496502 (\ file row column -> do
497- let column_ :: Int = column & readMaybeText & withDefault 1
498503 -- IntelliJ seems to number it's columns from 1 index
499- Ext.Common. execCombineStdOutErr " open" [" -na" , " IntelliJ IDEA CE.app" , " --args" , " --line" , T. unpack row, " --column" , show (column_ - 1 ), file] " "
504+ Ext.Common. c_ " open" [" -na" , " IntelliJ IDEA CE.app" , " --args" , " --line" , show row, " --column" , show (column - 1 ), file] " "
500505 )
501506
502507 , detectEditor " intellij"
503508 (Dir. doesDirectoryExist " /Applications/IntelliJ IDEA.app" )
504509 (\ file row column -> do
505- let column_ :: Int = column & readMaybeText & withDefault 1
506510 -- IntelliJ seems to number it's columns from 1 index
507- Ext.Common. execCombineStdOutErr " open" [" -na" , " IntelliJ IDEA.app" , " --args" , " --line" , T. unpack row, " --column" , show (column_ - 1 ), file] " "
511+ Ext.Common. c_ " open" [" -na" , " IntelliJ IDEA.app" , " --args" , " --line" , show row, " --column" , show (column - 1 ), file] " "
508512 )
509513 ]
510514
@@ -747,6 +751,14 @@ logger =
747751 atomicPutStrLn $ T. unpack $ TE. decodeUtf8 bs
748752 )
749753
754+
755+ noContentResponse :: Snap ()
756+ noContentResponse = do
757+ modifyResponse $ setResponseStatus 204 " No Content"
758+ r <- getResponse
759+ finishWith r
760+
761+
750762jsonResponse :: B. Builder -> Snap ()
751763jsonResponse s =
752764 do modifyResponse $ setContentType " application/json; charset=utf-8"
@@ -784,11 +796,11 @@ error503 s =
784796 r <- getResponse
785797 finishWith r
786798
787- error400 :: B. Builder -> Snap ()
788- error400 s =
799+ error400PlainText :: B. Builder -> Snap ()
800+ error400PlainText s =
789801 do modifyResponse $ setResponseStatus 400 " Bad Request"
790- modifyResponse $ setContentType " application/json ; charset=utf-8"
791- writeBuilder $ " { \" error \" : \" " <> s <> " \" } "
802+ modifyResponse $ setContentType " text/plain ; charset=utf-8"
803+ writeBuilder s
792804 r <- getResponse
793805 finishWith r
794806
0 commit comments