Skip to content

Commit

Permalink
Full autocomplete support in formula editor
Browse files Browse the repository at this point in the history
  • Loading branch information
albertprz committed Apr 7, 2024
1 parent 2af514e commit 601302a
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 106 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ for use for enterprise, personal & educational purposes.

## Project Goals

- [:heavy_check_mark:] Provide a no frills, minimalistic GUI supporting all basic spreadsheet functionality regarding navigation, cell management, formula evaluation & automatic cell updates.
- [✔️] Provide a no frills, minimalistic GUI supporting all basic spreadsheet functionality regarding navigation, cell management, formula evaluation & automatic cell updates.

- [:heavy_check_mark:] Expose a high level pure functional dynamic formula language interpreted at the browser, with expresiveness similar to the term level language in Haskell or Purescript, albeit with familiar syntax and idioms to popular spreadsheet applications and mainstream languages.
- [✔️] a high level pure functional dynamic formula language interpreted at the browser, with expresiveness similar to the term level language in Haskell or Purescript, albeit with familiar syntax and idioms to popular spreadsheet applications and mainstream languages.

- [:heavy_check_mark:] Expose a prelude library with commonly used functions and combinators, loaded at startup.
- [✔️] Expose a prelude library with commonly used functions and combinators, loaded at startup.

- [:heavy_check_mark:] Support formula edition with syntax highlighting and function signatures for the current function at the cursor.
- [✔️] Support formula edition with syntax highlighting and function signatures for the current function at the cursor.

- [:heavy_check_mark:] Support IDE like autocomplete for imported and module aliased top-level functions and operators.
- [✔️] Support IDE like autocomplete for imported and module aliased top-level functions and operators.

- Expose a view to query (possibly in a Hoogle / Pursuit fashion), view, update and upload new global functions & operators on a per module basis. This view would surface the same editing capatibilities as the formula box in the main spreadsheet view.

Expand Down
3 changes: 3 additions & 0 deletions src/CSS/ClassNames.purs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ suggestionsDropdown = ClassName "suggestions-dropdown"
suggestionOption :: ClassName
suggestionOption = ClassName "suggestion-option"

selectedSuggestionOption :: ClassName
selectedSuggestionOption = ClassName "selected-suggestion-option"

cellSyntax :: ClassName
cellSyntax = ClassName "cell-syntax"

Expand Down
6 changes: 3 additions & 3 deletions src/CSS/Common.purs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import Tecton.Internal (Length, Measure, px)
formulaFontSize :: Measure Length
formulaFontSize = px 21

suggestionsFontSize :: Measure Length
suggestionsFontSize = px 22

signatureFontSize :: Measure Length
signatureFontSize = px 24

Expand Down Expand Up @@ -66,6 +63,9 @@ lighterYellow = hex "#ffffcc"
blue :: Color
blue = hex "#0066cc"

lightBlue :: Color
lightBlue = hex "#6699ff"

orange :: Color
orange = hex "#ff6600"

Expand Down
11 changes: 7 additions & 4 deletions src/CSS/Table.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module App.CSS.Table where

import CSSPrelude

import App.CSS.ClassNames (formulaBoxContainer, functionSignature, functionSyntax, suggestionOption, suggestionsDropdown)
import App.CSS.Common (darkGreen, darkPink, formulaFontSize, signatureFontSize, suggestionsFontSize)
import App.CSS.ClassNames (formulaBoxContainer, functionSignature, functionSyntax, selectedSuggestionOption, suggestionOption, suggestionsDropdown)
import App.CSS.Common (darkGreen, darkPink, formulaFontSize, lightBlue, signatureFontSize)
import Tecton.Internal (Length, Measure)
import Tecton.Rule as Rule
import Type.Prelude (Proxy)
Expand Down Expand Up @@ -60,7 +60,7 @@ formulaCss = do

universal &. suggestionsDropdown ? Rule.do
position := fixed
width := rem 16
width := rem 10
backgroundColor := white

universal &. suggestionOption ? Rule.do
Expand All @@ -71,7 +71,10 @@ formulaCss = do
borderWidth := px 1
borderStyle := solid
borderColor := grey2
fontSize := suggestionsFontSize
fontSize := formulaFontSize

universal &. selectedSuggestionOption ? Rule.do
backgroundColor := lightBlue

universal &. functionSignature ? Rule.do
height := px 20
Expand Down
10 changes: 6 additions & 4 deletions src/Components/Table.purs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ initialState = const
, tableDependencies: HashMap.empty
, tableFormulas: HashMap.empty
, formulaCache: HashMap.empty
, rows: bottom .. mkRow 100
, multiSelection: NoSelection
, selectionState: NotStartedSelection
, draggedHeader: Nothing
, fnsMap: HashMap.empty
, operatorsMap: HashMap.empty
, aliasedModulesMap: HashMap.empty
, importedModulesMap: HashMap.empty
, rows: mkRow <$> (0 .. 100)
, multiSelection: NoSelection
, selectionState: NotStartedSelection
, draggedHeader: Nothing
, suggestions: []
, selectedSuggestionId: zero
}
39 changes: 25 additions & 14 deletions src/Components/Table/Handler.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import App.Components.Table.Formula (FormulaState(..))
import App.Components.Table.HandlerHelpers (cellArrowMove, cellMove, copyCells, deleteCells, insertFormula, loadPrelude, lookupFormula, pasteCells, refreshCells, selectAllCells, selectCell, subscribeSelectionChange, subscribeWindowResize)
import App.Components.Table.Models (Action(..), AppState, EventTransition(..))
import App.Components.Table.Selection (MultiSelection(..), SelectionState(..))
import App.Evaluator.Formula (mkLocalContext)
import App.Utils.Dom (actOnElementById, displayFnSig, displayFnSuggestions, emptyFnSig, emptyFnSuggestions, emptyFormulaBox, focusById, focusCell, focusCellElem, insertFormulaNewLine, performSyntaxHighlight, prevent, updateFormulaBox, withPrevent)
import App.Utils.Dom (actOnElementById, displayFnSig, displayFnSuggestions, emptyFnSig, emptyFormulaBox, focusById, focusCell, focusCellElem, insertFormulaNewLine, performAutoComplete, performSyntaxHighlight, prevent, updateFormulaBox, withPrevent)
import App.Utils.Event (ctrlKey, shiftKey, toEvent, toMouseEvent)
import App.Utils.HashMap (lookup2) as HashMap
import App.Utils.KeyCode (KeyCode(..), isModifierKeyCode)
Expand All @@ -35,8 +34,8 @@ handleAction Initialize = do
loadPrelude
actOnElementById formulaBoxId $ setContentEditable "true"
handleAction ResizeWindow
subscribeSelectionChange
subscribeWindowResize
subscribeSelectionChange

handleAction ResizeWindow = do
{ selectedCell } <- get
Expand Down Expand Up @@ -70,19 +69,32 @@ handleAction (WriteCell cell value) = do
}
refreshCells $ Set.singleton cell

handleAction (FormulaKeyDown Enter ev)
| ctrlKey ev = withPrevent ev insertFormula
handleAction (FormulaKeyDown (Just _) keyCode ev)
| keyCode `elem` [ ArrowUp, ArrowDown ] = withPrevent ev do
let next = if keyCode == ArrowUp then dec else inc
modify_ \st -> st
{ selectedSuggestionId =
clamp bottom
(wrap $ dec $ length st.suggestions)
$ next st.selectedSuggestionId
}

handleAction (FormulaKeyDown (Just suggestion) keyCode ev)
| keyCode `elem` [ Enter, Tab ] =
withPrevent ev $ performAutoComplete $ show suggestion

handleAction (FormulaKeyDown Enter ev) =
withPrevent ev (insertFormulaNewLine *> performSyntaxHighlight)
handleAction (FormulaKeyDown _ Enter ev)
| ctrlKey ev = withPrevent ev insertFormula
| otherwise = withPrevent ev
(insertFormulaNewLine *> performSyntaxHighlight)

handleAction (FormulaKeyDown Tab ev) =
handleAction (FormulaKeyDown _ Tab ev) =
withPrevent ev $ focusCell =<< gets _.selectedCell

handleAction (FormulaKeyDown (CharKeyCode 'G') ev)
handleAction (FormulaKeyDown _ (CharKeyCode 'G') ev)
| ctrlKey ev = withPrevent ev $ focusById formulaCellInputId

handleAction (FormulaKeyDown _ _) =
handleAction (FormulaKeyDown _ _ _) =
modify_ _ { formulaState = UnknownFormula }

handleAction (FormulaKeyUp keyCode _) =
Expand Down Expand Up @@ -117,7 +129,6 @@ handleAction (DoubleClickCell cell ev) = withPrevent ev do
handleAction (FocusInCell cell _) = do
formulaText <- _.formulaText <$$> lookupFormula cell
emptyFnSig
emptyFnSuggestions
case formulaText of
Just x -> updateFormulaBox x
Nothing -> emptyFormulaBox
Expand All @@ -126,6 +137,7 @@ handleAction (FocusInCell cell _) = do
, formulaState =
if isJust formulaText then ValidFormula
else UnknownFormula
, suggestions = []
}

handleAction (KeyDown x ev) | x `elem` [ ArrowLeft, CharKeyCode 'H' ] =
Expand Down Expand Up @@ -299,6 +311,5 @@ handleAction (DragHeader _ _ _) =
pure unit

handleAction SelectionChange = do
ctx <- mkLocalContext <$> get
displayFnSig ctx
displayFnSuggestions ctx
displayFnSuggestions
displayFnSig =<< get
13 changes: 8 additions & 5 deletions src/Components/Table/Models.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import App.Components.Table.Formula (Formula, FormulaId, FormulaState)
import App.Components.Table.Selection (MultiSelection, SelectionState)
import App.SyntaxTree.Common (Module, QVar, QVarOp)
import App.SyntaxTree.FnDef (FnInfo, OpInfo)
import App.Utils.Formula (SuggestionId, SuggestionTerm)
import App.Utils.KeyCode (KeyCode)
import Web.HTML.Event.DragEvent (DragEvent)
import Web.UIEvent.FocusEvent (FocusEvent)
Expand All @@ -25,14 +26,16 @@ type AppState =
, tableFormulas :: HashMap Cell FormulaId
, tableDependencies :: HashMap Cell (NonEmptySet FormulaId)
, formulaCache :: HashMap FormulaId Formula
, rows :: MinLenVect 1 Row
, multiSelection :: MultiSelection
, selectionState :: SelectionState
, draggedHeader :: Maybe Header
, fnsMap :: HashMap QVar FnInfo
, operatorsMap :: HashMap QVarOp OpInfo
, aliasedModulesMap :: HashMap (Module /\ Module) (Set Module)
, importedModulesMap :: HashMap Module (Set Module)
, rows :: MinLenVect 1 Row
, multiSelection :: MultiSelection
, selectionState :: SelectionState
, draggedHeader :: Maybe Header
, suggestions :: Array SuggestionTerm
, selectedSuggestionId :: SuggestionId
}

data Action
Expand All @@ -46,7 +49,7 @@ data Action
| FocusInCell Cell FocusEvent
| KeyDown KeyCode KeyboardEvent
| KeyUp KeyCode KeyboardEvent
| FormulaKeyDown KeyCode KeyboardEvent
| FormulaKeyDown (Maybe SuggestionTerm) KeyCode KeyboardEvent
| FormulaKeyUp KeyCode KeyboardEvent
| SelectedCellInputKeyDown KeyCode KeyboardEvent
| FormulaCellInputKeyDown KeyCode KeyboardEvent
Expand Down
43 changes: 30 additions & 13 deletions src/Components/Table/Renderer.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ module App.Components.Table.Renderer where
import FatPrelude hiding (div)
import Prim hiding (Row)

import App.CSS.ClassNames (aboveSelection, atLeftSelection, atRightSelection, belowSelection, columnHeader, copySelection, cornerHeader, formulaBox, formulaBoxContainer, formulaCellInput, formulaSectionContainer, functionSignature, inSelection, mainContainer, rowHeader, selectedCellInput, selectedHeader, selectedSheetCell, sheetCell, suggestionsDropdown)
import App.CSS.ClassNames (aboveSelection, atLeftSelection, atRightSelection, belowSelection, columnHeader, copySelection, cornerHeader, formulaBox, formulaBoxContainer, formulaCellInput, formulaSectionContainer, functionSignature, inSelection, mainContainer, materialIcons, rowHeader, selectedCellInput, selectedHeader, selectedSheetCell, selectedSuggestionOption, sheetCell, suggestionOption, suggestionsDropdown)
import App.CSS.Ids (cellId, formulaBoxId, formulaCellInputId, functionSignatureId, selectedCellInputId, suggestionsDropdownId)
import App.Components.Table.Cell (Cell, CellValue, Column, Header(..), Row, allColumns, cellParser, parseCellValue, showCell)
import App.Components.Table.Formula (formulaStateToClass)
import App.Components.Table.Models (Action(..), AppState, EventTransition(..))
import App.Components.Table.Selection (SelectionState(..), isCellAboveSelection, isCellAtLeftSelection, isCellAtRightSelection, isCellBelowSelection, isCellInSelection, isColumnSelected, isRowSelected)
import App.Utils.Formula (SuggestionTerm)
import App.Utils.KeyCode (mkKeyAction)
import Bookhound.Parser (runParser)
import Data.Array ((!!))
import Data.Array as Array
import Data.HashMap as HashMap
import Halogen.HTML (ClassName, ComponentHTML, HTML, div, input, table, tbody_, td, text, th, thead_, tr_)
import Halogen.HTML.Elements (i)
import Halogen.HTML.Events (onClick, onDoubleClick, onDragOver, onDragStart, onDrop, onFocusIn, onKeyDown, onKeyUp, onMouseDown, onMouseOver, onMouseUp, onValueChange, onWheel)
import Halogen.HTML.Properties (AutocompleteType(..), InputType(..), autocomplete, class_, classes, draggable, id, readOnly, style, tabIndex, type_, value)
import Halogen.HTML.Properties (AutocompleteType(..), InputType(..), autocomplete, class_, classes, draggable, id, readOnly, spellcheck, style, tabIndex, type_, value)

render :: forall cs m. AppState -> ComponentHTML Action cs m
render
Expand All @@ -25,6 +28,8 @@ render
, activeFormula
, formulaState
, selectionState
, suggestions
, selectedSuggestionId
} =
div [ class_ mainContainer ]
[ div [ class_ formulaSectionContainer ]
Expand All @@ -40,7 +45,9 @@ render
[ div
[ id $ show formulaBoxId
, classes [ formulaBox, formulaStateToClass formulaState ]
, onKeyDown $ mkKeyAction FormulaKeyDown
, spellcheck false
, onKeyDown $ mkKeyAction $ FormulaKeyDown
(suggestions !! unwrap selectedSuggestionId)
, onKeyUp $ mkKeyAction FormulaKeyUp
, onFocusIn FocusInFormula
]
Expand All @@ -49,12 +56,12 @@ render
[ id $ show functionSignatureId
, classes [ functionSignature ]
]
[ text mempty ]
[]
, div
[ id $ show suggestionsDropdownId
, class_ suggestionsDropdown
]
[]
(mapWithIndex (renderSuggestion st) suggestions)
]
, input
[ id $ show formulaCellInputId
Expand All @@ -80,22 +87,32 @@ render
where
parseCell = hush <<< runParser cellParser

renderSuggestion :: forall i. AppState -> Int -> SuggestionTerm -> HTML i Action
renderSuggestion { selectedSuggestionId } n suggestionTerm = div
[ classes $ [ suggestionOption ]
<>? (wrap n == selectedSuggestionId)
/\ selectedSuggestionOption
]
[ i
[ class_ materialIcons ]
[ text "functions" ]
, text $ show suggestionTerm
]

renderHeader :: forall i. AppState -> HTML i Action
renderHeader st =
thead_
[ tr_
$ toArray
$ cons
renderHeaderCorner
( th
[ class_ cornerHeader
, onClick $ ClickHeader CornerHeader
]
[ text mempty ]
)
(renderColumnHeader st <$> allColumns)
]
where
renderHeaderCorner =
th
[ class_ cornerHeader
, onClick $ ClickHeader CornerHeader
]
[ text mempty ]

renderBody :: forall i. AppState -> HTML i Action
renderBody
Expand Down
Loading

0 comments on commit 601302a

Please sign in to comment.