@@ -1180,14 +1180,29 @@ module ControlPanel = {
11801180 }
11811181 }
11821182
1183+ let commandWithKeyboardShortcut = (commandName , ~key ) => {
1184+ let userAgent = window .navigator .userAgent
1185+ if userAgent -> String .includes ("iPhone" ) || userAgent -> String .includes ("Android" ) {
1186+ commandName
1187+ } else if userAgent -> String .includes ("Mac" ) {
1188+ ` ${commandName} (⌘ + ${key})`
1189+ } else {
1190+ ` ${commandName} (Ctrl + ${key})`
1191+ }
1192+ }
1193+
11831194 @react.component
11841195 let make = (
11851196 ~actionIndicatorKey : string ,
11861197 ~state : CompilerManagerHook .state ,
11871198 ~dispatch : CompilerManagerHook .action => unit ,
1188- ~editorCode : React .ref <string >,
1199+ ~editorRef : React .ref <option < CodeMirror . editorInstance > >,
11891200 ~setCurrentTab : (tab => tab ) => unit ,
11901201 ) => {
1202+ let format = () =>
1203+ editorRef .current -> Option .forEach (editorInstance =>
1204+ dispatch (Format (CodeMirror .editorGetValue (editorInstance )))
1205+ )
11911206 React .useEffect (() => {
11921207 switch state {
11931208 | Ready (_ )
@@ -1202,6 +1217,9 @@ module ControlPanel = {
12021217 event -> ReactEvent .Keyboard .preventDefault
12031218 setCurrentTab (_ => Output )
12041219 dispatch (RunCode )
1220+ | (true , "s" ) =>
1221+ event -> ReactEvent .Keyboard .preventDefault
1222+ format ()
12051223 | _ => ()
12061224 }
12071225 }
@@ -1220,7 +1238,7 @@ module ControlPanel = {
12201238 | Ready (_ ) =>
12211239 let onFormatClick = evt => {
12221240 ReactEvent .Mouse .preventDefault (evt )
1223- dispatch ( Format ( editorCode . current ) )
1241+ format ( )
12241242 }
12251243
12261244 let autoRun = switch state {
@@ -1230,18 +1248,6 @@ module ControlPanel = {
12301248 | _ => false
12311249 }
12321250
1233- let runButtonText = {
1234- let userAgent = window .navigator .userAgent
1235- let run = "Run"
1236- if userAgent -> String .includes ("iPhone" ) || userAgent -> String .includes ("Android" ) {
1237- run
1238- } else if userAgent -> String .includes ("Mac" ) {
1239- ` ${run} (⌘ + E)`
1240- } else {
1241- ` ${run} (Ctrl + E)`
1242- }
1243- }
1244-
12451251 <div className = "flex flex-row gap-x-2" dataTestId = "control-panel" >
12461252 <ToggleButton
12471253 checked = autoRun
@@ -1261,9 +1267,11 @@ module ControlPanel = {
12611267 dispatch (RunCode )
12621268 }}
12631269 >
1264- {React .string (runButtonText )}
1270+ {React .string (commandWithKeyboardShortcut ("Run" , ~key = "E" ))}
1271+ </Button >
1272+ <Button onClick = onFormatClick >
1273+ {React .string (commandWithKeyboardShortcut ("Format" , ~key = "S" ))}
12651274 </Button >
1266- <Button onClick = onFormatClick > {React .string ("Format" )} </Button >
12671275 <ShareButton actionIndicatorKey />
12681276 </div >
12691277 | _ => React .null
@@ -1513,6 +1521,8 @@ let initialReContent = `Js.log("Hello Reason 3.6!");`
15131521@react.component
15141522let make = (~bundleBaseUrl : string , ~versions : array <string >) => {
15151523 let (searchParams , _ ) = ReactRouter .useSearchParams ()
1524+ let containerRef = React .useRef (Nullable .null )
1525+ let editorRef : React .ref <option <CodeMirror .editorInstance >> = React .useRef (None )
15161526
15171527 let versions =
15181528 versions
@@ -1584,7 +1594,10 @@ let make = (~bundleBaseUrl: string, ~versions: array<string>) => {
15841594 // We don't count to infinity. This value is only required to trigger
15851595 // rerenders for specific components (ActivityIndicator)
15861596 let (actionCount , setActionCount ) = React .useState (_ => 0 )
1587- let onAction = _ => setActionCount (prev => prev > 1000000 ? 0 : prev + 1 )
1597+ let onAction = React .useCallback (
1598+ _ => setActionCount (prev => prev > 1000000 ? 0 : prev + 1 ),
1599+ [setActionCount ],
1600+ )
15881601 let (compilerState , compilerDispatch ) = useCompilerManager (
15891602 ~bundleBaseUrl ,
15901603 ~initialVersion ?,
@@ -1594,10 +1607,11 @@ let make = (~bundleBaseUrl: string, ~versions: array<string>) => {
15941607 ~initialLang ,
15951608 ~onAction ,
15961609 ~versions ,
1597- (),
15981610 )
15991611
16001612 let (keyMap , setKeyMap ) = React .useState (() => CodeMirror .KeyMap .Default )
1613+ let typingTimer = React .useRef (None )
1614+ let timeoutCompile = React .useRef (_ => ())
16011615
16021616 React .useEffect (() => {
16031617 setKeyMap (_ =>
@@ -1609,50 +1623,82 @@ let make = (~bundleBaseUrl: string, ~versions: array<string>) => {
16091623 None
16101624 }, [])
16111625
1626+ React .useEffect (() => {
1627+ switch containerRef .current {
1628+ | Value (parent ) =>
1629+ let mode = switch compilerState {
1630+ | Ready ({targetLang : Reason }) => "reason"
1631+ | Ready ({targetLang : Res }) => "rescript"
1632+ | _ => "rescript"
1633+ }
1634+ let config : CodeMirror .editorConfig = {
1635+ parent ,
1636+ initialValue : initialContent ,
1637+ mode ,
1638+ readOnly : false ,
1639+ lineNumbers : true ,
1640+ lineWrapping : false ,
1641+ keyMap : CodeMirror .KeyMap .Default ,
1642+ errors : [],
1643+ hoverHints : [],
1644+ onChange : {
1645+ value => {
1646+ switch typingTimer .current {
1647+ | None => ()
1648+ | Some (timer ) => clearTimeout (timer )
1649+ }
1650+ let timer = setTimeout (~handler = () => {
1651+ timeoutCompile .current (value )
1652+ typingTimer .current = None
1653+ }, ~timeout = 100 )
1654+ typingTimer .current = Some (timer )
1655+ }
1656+ },
1657+ }
1658+ let editor = CodeMirror .createEditor (config )
1659+ editorRef .current = Some (editor )
1660+ Some (() => CodeMirror .editorDestroy (editor ))
1661+ | Null | Undefined => None
1662+ }
1663+ }, [])
1664+
16121665 React .useEffect (() => {
16131666 Dom .Storage2 .localStorage -> Dom .Storage2 .setItem ("vimMode" , CodeMirror .KeyMap .toString (keyMap ))
1667+ editorRef .current -> Option .forEach (CodeMirror .editorSetKeyMap (_ , keyMap ))
16141668 None
16151669 }, [keyMap ])
16161670
16171671 let editorCode = React .useRef (initialContent )
16181672
1619- /* In case the compiler did some kind of syntax conversion / reformatting,
1620- we take any success results and set the editor code to the new formatted code */
1621- switch compilerState {
1622- | Ready ({result : FinalResult .Nothing } as ready ) =>
1623- try {
1624- compilerDispatch (CompileCode (ready .targetLang , editorCode .current ))
1625- } catch {
1626- | err => Console .error (err )
1627- }
1628- | Ready ({result : FinalResult .Conv (Api .ConversionResult .Success ({code }))}) =>
1629- editorCode .current = code
1630- | _ => ()
1631- }
1632-
16331673 /*
1634- The codemirror state and the compilerState are not dependent on eachother ,
1674+ The codemirror state and the compilerState are not dependent on each other ,
16351675 so we need to sync a timeoutCompiler function with our compilerState to be
16361676 able to do compilation on code changes.
16371677
16381678 The typingTimer is a debounce mechanism to prevent compilation during editing
16391679 and will be manipulated by the codemirror onChange function.
16401680 */
1641- let typingTimer = React .useRef (None )
1642- let timeoutCompile = React .useRef (() => ())
1643-
16441681 React .useEffect (() => {
1645- timeoutCompile .current = () =>
1682+ timeoutCompile .current = code =>
16461683 switch compilerState {
1647- | Ready (ready ) =>
1648- try {
1649- compilerDispatch (CompileCode (ready .targetLang , editorCode .current ))
1650- } catch {
1651- | err => Console .error (err )
1652- }
1684+ | Ready ({targetLang }) => compilerDispatch (CompileCode (targetLang , code ))
16531685 | _ => ()
16541686 }
16551687
1688+ switch (compilerState , editorRef .current ) {
1689+ | (Ready ({result : FinalResult .Nothing , targetLang }), Some (editorInstance )) =>
1690+ try {
1691+ compilerDispatch (CompileCode (targetLang , CodeMirror .editorGetValue (editorInstance )))
1692+ } catch {
1693+ | err => Console .error (err )
1694+ }
1695+ | (
1696+ Ready ({result : FinalResult .Conv (Api .ConversionResult .Success ({code }))}),
1697+ Some (editorInstance ),
1698+ ) =>
1699+ CodeMirror .editorSetValue (editorInstance , code )
1700+ | _ => ()
1701+ }
16561702 None
16571703 }, (compilerState , compilerDispatch ))
16581704
@@ -1856,12 +1902,6 @@ let make = (~bundleBaseUrl: string, ~versions: array<string>) => {
18561902 | _ => []
18571903 }
18581904
1859- let mode = switch compilerState {
1860- | Ready ({targetLang : Reason }) => "reason"
1861- | Ready ({targetLang : Res }) => "rescript"
1862- | _ => "rescript"
1863- }
1864-
18651905 let (currentTab , setCurrentTab ) = React .useState (_ => JavaScript )
18661906
18671907 let disabled = false
@@ -1951,8 +1991,8 @@ let make = (~bundleBaseUrl: string, ~versions: array<string>) => {
19511991 actionIndicatorKey = {Int .toString (actionCount )}
19521992 state = compilerState
19531993 dispatch = compilerDispatch
1954- editorCode
19551994 setCurrentTab
1995+ editorRef
19561996 />
19571997 <div
19581998 className = {` flex ${layout == Column ? "flex-col" : "flex-row" }` }
@@ -1965,26 +2005,9 @@ let make = (~bundleBaseUrl: string, ~versions: array<string>) => {
19652005 ? "w-full"
19662006 : "w-[50%]" }` }
19672007 >
1968- <CodeMirror
2008+ <div
19692009 className = "bg-gray-100 h-full"
1970- mode
1971- hoverHints = cmHoverHints
1972- errors = cmErrors
1973- value = {editorCode .current }
1974- onChange = {value => {
1975- editorCode .current = value
1976-
1977- switch typingTimer .current {
1978- | None => ()
1979- | Some (timer ) => clearTimeout (timer )
1980- }
1981- let timer = setTimeout (~handler = () => {
1982- timeoutCompile .current ()
1983- typingTimer .current = None
1984- }, ~timeout = 100 )
1985- typingTimer .current = Some (timer )
1986- }}
1987- keyMap
2010+ ref = {ReactDOM .Ref .domRef ((Obj .magic (containerRef ): React .ref <Nullable .t <Dom .element >>))}
19882011 />
19892012 </div >
19902013 // Separator
0 commit comments