diff --git a/editor/Elmz/Trie.elm b/editor/Elmz/Trie.elm new file mode 100644 index 000000000..1d3f81908 --- /dev/null +++ b/editor/Elmz/Trie.elm @@ -0,0 +1,36 @@ +module Elmz.Trie where + +import List +import List ((::)) + +type Trie k v = Trie (Maybe v) (List (k, Trie k v)) + +empty : Trie k v +empty = Trie Nothing [] + +insert : List k -> v -> Trie k v -> Trie k v +insert k v (Trie v0 children) = case k of + [] -> Trie (Just v) children + h :: t -> let (yes,no) = List.partition (\(k2,_) -> k2 == h) children + in case yes of + [] -> Trie v0 ((h, insert t v empty) :: no) + (_,child) :: _ -> Trie v0 ((h, insert t v child) :: no) + +lookup : List k -> Trie k v -> Maybe v +lookup k (Trie v children) = case k of + [] -> v + h :: t -> let f (k,v) = if k == h then Just v else Nothing + in case List.filterMap f children of + child :: _ -> lookup t child + [] -> Nothing + +delete : List k -> Trie k v -> Trie k v +delete k (Trie v children) = case k of + [] -> Trie Nothing children + h :: t -> let f2 (k,child) = if k == h then (k, delete t child) else (k, child) + in Trie v (List.map f2 children) + +contains : List k -> Trie k v -> Bool +contains k t = case lookup k t of + Nothing -> False + Just _ -> True diff --git a/editor/Unison/Editor.elm b/editor/Unison/Editor.elm new file mode 100644 index 000000000..385e18957 --- /dev/null +++ b/editor/Unison/Editor.elm @@ -0,0 +1,103 @@ +module Unison.Editor (Model) where + +import Dict +import Dict (Dict) +import Elmz.Layout (Containment,Layout,Pt,Region) +import Elmz.Layout as Layout +import Elmz.Selection1D as Selection1D +import Elmz.Trie (Trie) +import Elmz.Trie as Trie +import Graphics.Input.Field as Field +import Graphics.Element as Element +import Graphics.Element (Element) +import Result +import Unison.Explorer as Explorer +import Unison.Hash (Hash) +import Unison.Metadata as Metadata +import Unison.Path (Path) +import Unison.Path as Path +import Unison.Scope (Scope) +import Unison.Scope as Scope +import Unison.Styles as Styles +import Unison.Term (Term) +import Unison.Term as Term +import Unison.View as View +import Signal + +type alias Model = + { overall : Term + , scope : Maybe Scope + , explorer : Explorer.Model + , explorerValues : List Term + , explorerSelection : Selection1D.Model + , dependents : Trie Path.E (List Path) + , overrides : Trie Path.E (Layout View.L) + , hashes : Trie Path.E Hash } + +type alias Action = Model -> Model + + + +viewTerm : Int -> Term -> Layout View.L +viewTerm availableWidth term = + View.layout term { rootMetadata = Metadata.anonymousTerm + , availableWidth = availableWidth + , metadata h = Metadata.anonymousTerm + , overrides x = Nothing } + +type alias Sink a = a -> Signal.Message + +type alias Context = + { availableWidth : Int + , searchbox : Sink Field.Content + , explorerActive : Sink Bool } + +view : Context -> Model -> (Layout View.L, Layout (Result Containment Int)) +view ctx model = + let termLayout = viewTerm ctx.availableWidth model.overall + + highlighted : Maybe Region + highlighted = case model.scope of + Nothing -> Nothing + Just scope -> Layout.region Path.startsWith .path termLayout scope.focus + |> Layout.selectableLub .selectable + + highlightLayer : Element + highlightLayer = case highlighted of + Nothing -> Element.empty + Just region -> Styles.selection region + + highlightedTermLayout : Layout View.L + highlightedTermLayout = + Layout.transform (\e -> Element.layers [e, highlightLayer]) termLayout + + explorerTopLeft : Pt + explorerTopLeft = case highlighted of + Nothing -> Pt 0 0 + Just region -> { x = region.topLeft.x, y = region.topLeft.y + region.height } + + explorerLayout : Layout (Result Containment Int) + explorerLayout = + Explorer.view explorerTopLeft + ctx.searchbox + ctx.explorerActive + model.explorer + + in (highlightedTermLayout, explorerLayout) +-- +-- viewExplorer : Model -> Layout +-- view : Int -> Term -> Layout View.L +-- view availableWidth term = +-- let rendered : Signal (L.Layout { path : Path, selectable : Bool }) +-- rendered = layout <~ Signals.steady (100 * Time.millisecond) Window.width ~ term +-- in + +todo : a +todo = todo + +-- might want to just add Panel, Cell constructors +-- Panel Term + +{-| For each subpanel path, what are the paths to other subpanels that it depends on? -} +-- localDependencies : (Path -> Maybe (Hash,Term)) -> Term -> Path -> [Path] +-- localDependencies sourceOf e = todo diff --git a/editor/Unison/Explorer.elm b/editor/Unison/Explorer.elm index 42d5178ce..78c3bc1c7 100644 --- a/editor/Unison/Explorer.elm +++ b/editor/Unison/Explorer.elm @@ -1,7 +1,7 @@ module Unison.Explorer where import Debug -import Elmz.Layout (Layout,Pt,Region) +import Elmz.Layout (Layout,Pt,Region,Containment(Inside,Outside)) import Elmz.Layout as Layout import Elmz.Maybe import Elmz.Movement as Movement @@ -24,14 +24,6 @@ import Time import Unison.Styles as Styles import Window --- model for overall editor is --- Term --- Maybe Scope.Model --- Explorer.Model --- can get a bit fancier with tracking dependencies, --- result of previous evaluations --- mapping from paths to hashes - type alias Model = Maybe { isKeyboardOpen : Bool , prompt : String @@ -42,9 +34,6 @@ type alias Model = Maybe type alias Action = Model -> Model --- actions for setting the instructions, --- completions, invalid completions, input, and keyboard open - zero : Model zero = Just { isKeyboardOpen = False @@ -54,6 +43,36 @@ zero = Just , completions = [] , invalidCompletions = [] } +setPrompt : Signal String -> Signal Action +setPrompt event = + let f s model = Maybe.map (\m -> { m | prompt <- s }) model + in Signal.map f event + +openKeyboard : Signal () -> Signal Action +openKeyboard event = + let f _ model = Maybe.map (\m -> { m | isKeyboardOpen <- True }) model + in Signal.map f event + +setInput : Signal Field.Content -> Signal Action +setInput content = + let f c model = Maybe.map (\m -> { m | input <- c }) model + in Signal.map f content + +setInstructions : Signal Element -> Signal Action +setInstructions e = + let f e model = Maybe.map (\m -> { m | instructions <- e }) model + in Signal.map f e + +setCompletions : Signal (List Element) -> Signal Action +setCompletions e = + let f e model = Maybe.map (\m -> { m | completions <- e }) model + in Signal.map f e + +setInvalidCompletions : Signal (List Element) -> Signal Action +setInvalidCompletions e = + let f e model = Maybe.map (\m -> { m | invalidCompletions <- e }) model + in Signal.map f e + clicks : { tl | click : Signal (), inside : Signal Bool, allowOpen : Signal Bool } -> Signal Action clicks {click,inside,allowOpen} = let f inside allowOpen model = case model of @@ -68,35 +87,11 @@ enters {down,allowOpen} = Just _ -> Nothing in Signal.sampleOn down (Signal.map2 f down allowOpen) --- may need another Signal Bool input, which lets the explorer be closed 'externally' --- or may want a `Signal Pt` which lets origin be moved --- todo: can replace all Err with the zero Model, and foldp over this to get our Model states -{- -actions : { enter : Signal Bool - , click : Signal () - , mouse : Signal (Int,Int) - , isOpen : Signal Bool - , upDown : Signal Movement.D1 - , completions : Signal - -> Signal () - -> Signal (Int,Int) - -> Signal Bool - -> Signal Movement.D1 - -> Signal (List v) - -> Signal (Action v) -actions {enter,click,mouse,isOpen,upDown} values = - let merge = Signals.mergeWith (\a1 a2 model -> a1 model `Result.andThen` a2) - in completions values `merge` - movements upDown `merge` - clicks click isOpen values `merge` - enters enter values --} - type alias Sink a = a -> Signal.Message -view : Pt -> Sink Field.Content -> Sink Bool -> Model -> Layout (Maybe Int) +view : Pt -> Sink Field.Content -> Sink Bool -> Model -> Layout (Result Containment Int) view origin searchbox active model = case model of - Nothing -> Layout.empty Nothing + Nothing -> Layout.empty (Result.Err Outside) Just s -> let ok = not (List.isEmpty s.completions) statusColor = Styles.statusColor ok @@ -105,20 +100,26 @@ view origin searchbox active model = case model of s.prompt s.input insertion = Styles.carotUp 7 statusColor - status = Layout.embed Nothing s.instructions + inside = Result.Err Inside + status = Layout.embed inside s.instructions |> Layout.transform (Input.clickable (active True)) - renderCompletion i e = Layout.embed (Just i) (Input.clickable (active False) e) - invalids = List.map (Layout.embed Nothing) s.invalidCompletions - top = Layout.embed Nothing (Input.clickable (active True) fld) + renderCompletion i e = Layout.embed (Result.Ok i) + (Input.clickable (active False) e) + invalids = List.map (Layout.embed inside) s.invalidCompletions + top = Layout.embed inside (Input.clickable (active True) fld) |> Layout.transform (Input.clickable (active True)) - spacer = Layout.embed Nothing (E.spacer 1 7) - bot = Styles.explorerCells Nothing <| + spacer = Layout.embed inside (E.spacer 1 7) + bot = Styles.explorerCells inside <| status :: List.indexedMap renderCompletion s.completions `List.append` invalids top' = Layout.transform (E.width (Layout.widthOf bot)) top - box = Layout.above Nothing - (Layout.embed Nothing (E.beside (E.spacer 14 1) insertion)) - (Layout.above Nothing (Layout.above (Layout.tag top) top' spacer) bot) + box = Layout.above inside + (Layout.embed inside (E.beside (E.spacer 14 1) insertion)) + (Layout.above inside (Layout.above (Layout.tag top) top' spacer) bot) boxTopLeft = origin h = boxTopLeft.y + Layout.heightOf box + 50 - in Layout.container Nothing (boxTopLeft.x + Layout.widthOf box) h boxTopLeft box + in Layout.container (Result.Err Outside) + (boxTopLeft.x + Layout.widthOf box) + h + boxTopLeft + box diff --git a/editor/Unison/Path.elm b/editor/Unison/Path.elm index 1fafec613..780000c8a 100644 --- a/editor/Unison/Path.elm +++ b/editor/Unison/Path.elm @@ -9,6 +9,7 @@ import Elmz.Json.Encoder (Encoder) import Elmz.Json.Decoder as Decoder import Json.Decode (Decoder) import Json.Decode as Decode +import String type E = Fn -- ^ Points at function in a function application @@ -17,6 +18,16 @@ type E | Index Int -- ^ Points into a `Vector` literal type alias Path = List E +type alias Key = String + +key : Path -> Key +key path = + let f e = case e of + Fn -> "Fn" + Arg -> "Arg" + Body -> "Body" + Index i -> toString i + in String.join "," (List.map f path) snoc : Path -> E -> Path snoc p e = p ++ [e] diff --git a/editor/Unison/Styles.elm b/editor/Unison/Styles.elm index e83589b7b..fe2e2563c 100644 --- a/editor/Unison/Styles.elm +++ b/editor/Unison/Styles.elm @@ -97,19 +97,19 @@ explorerCells k ls = in L.intersperseVertical hsep cs |> L.transform (\e -> E.layers [e, outlineOf midnightBlue 6 e]) -selection : Element -> Region -> Element -selection e = selectionLayer highlight { width = E.widthOf e, height = E.heightOf e } +selection : Region -> Element +selection = selectionLayer highlight -explorerSelection : Element -> Region -> Element -explorerSelection e = selectionLayer highlightExplorer { width = E.widthOf e, height = E.heightOf e } +explorerSelection : Region -> Element +explorerSelection = selectionLayer highlightExplorer -selectionLayer : (Int -> Int -> Element) -> { width : Int, height : Int } -> Region -> Element -selectionLayer highlight l r = +selectionLayer : (Int -> Int -> Element) -> Region -> Element +selectionLayer highlight r = let hl = highlight r.width r.height n = 1 - in E.container l.width - l.height + in E.container (r.topLeft.x + r.width) + (r.topLeft.y + r.height) (E.topLeftAt (E.absolute (r.topLeft.x)) (E.absolute (r.topLeft.y))) hl @@ -149,8 +149,8 @@ contain e = spinner : Signal Element spinner = - let pct n = toFloat (n%30) / 30.0 - t = Signal.map pct (Signals.count (Time.fps 30)) + let pct n = toFloat (n%60) / 60.0 + t = Signal.map pct (Signals.count (Time.fps 60)) rect = E.color midnightBlue (E.spacer 5 10) sep = E.spacer 1 1 render pct = E.flow E.right