@ -7,7 +7,7 @@ import {DataRowModel} from 'app/client/models/DataRowModel';
import { ColumnRec } from 'app/client/models/DocModel' ;
import { ViewFieldRec } from 'app/client/models/entities/ViewFieldRec' ;
import { reportError } from 'app/client/models/errors' ;
import { GRIST _FORMULA_ASSISTANT} from 'app/client/models/features' ;
import { HAS _FORMULA_ASSISTANT} from 'app/client/models/features' ;
import { hoverTooltip } from 'app/client/ui/tooltips' ;
import { textButton } from 'app/client/ui2018/buttons' ;
import { colors , testId , theme , vars } from 'app/client/ui2018/cssVars' ;
@ -53,7 +53,7 @@ export class FormulaEditor extends NewBaseEditor {
public isDetached = Observable . create ( this , false ) ;
protected options : IFormulaEditorOptions ;
private _ formul aEditor: any ;
private _ ace Editor: any ;
private _dom : HTMLElement ;
private _editorPlacement ! : EditorPlacement ;
private _placementHolder = Holder . create ( this ) ;
@ -71,7 +71,7 @@ export class FormulaEditor extends NewBaseEditor {
this . _isEmpty = Computed . create ( this , this . editorState , ( _use , state ) = > state === '' ) ;
this . _ formul aEditor = AceEditor . create ( {
this . _ ace Editor = AceEditor . create ( {
// A bit awkward, but we need to assume calcSize is not used until attach() has been called
// and _editorPlacement created.
column : options.column ,
@ -145,14 +145,14 @@ export class FormulaEditor extends NewBaseEditor {
// the DOM to update before resizing.
this . autoDispose ( errorDetails . addListener ( ( ) = > setTimeout ( this . resize . bind ( this ) , 0 ) ) ) ;
this . _canDetach = Boolean ( GRIST_FORMULA_ASSISTANT( ) . get ( ) && options. canDetach && ! options . readonly ) ;
this . _canDetach = Boolean ( options. canDetach && ! options . readonly ) ;
this . autoDispose ( this . _ formul aEditor) ;
this . autoDispose ( this . _ ace Editor) ;
// Show placeholder text when the formula is blank.
this . _isEmpty . addListener ( ( ) = > this . _updateEditorPlaceholder ( ) ) ;
// Update the placeholder text when expanding or collapsing the editor .
// Disable undo/redo while the editor is detached .
this . isDetached . addListener ( ( isDetached ) = > {
// TODO: look into whether we can support undo/redo while the editor is detached.
if ( isDetached ) {
@ -160,8 +160,6 @@ export class FormulaEditor extends NewBaseEditor {
} else {
options . gristDoc . getUndoStack ( ) . enable ( ) ;
}
this . _updateEditorPlaceholder ( ) ;
} ) ;
this . onDispose ( ( ) = > {
@ -203,22 +201,22 @@ export class FormulaEditor extends NewBaseEditor {
) ,
cssFormulaEditor . cls ( '-detached' , this . isDetached ) ,
dom ( 'div.formula_editor.formula_field_edit' , testId ( 'formula-editor' ) ,
this . _ formul aEditor. buildDom ( ( aceObj : any ) = > {
this . _ ace Editor. buildDom ( ( aceObj : any ) = > {
aceObj . setFontSize ( 11 ) ;
aceObj . setHighlightActiveLine ( false ) ;
aceObj . getSession ( ) . setUseWrapMode ( false ) ;
aceObj . renderer . setPadding ( 0 ) ;
const val = initialValue ;
const pos = Math . min ( options . cursorPos , val . length ) ;
this . _ formul aEditor. setValue ( val , pos ) ;
this . _ formul aEditor. attachCommandGroup ( aceCommands ) ;
this . _ ace Editor. setValue ( val , pos ) ;
this . _ ace Editor. attachCommandGroup ( aceCommands ) ;
// enable formula editing if state was passed
if ( options . state || options . readonly ) {
editingFormula ( true ) ;
}
if ( options . readonly ) {
this . _ formul aEditor. enable ( false ) ;
this . _ ace Editor. enable ( false ) ;
aceObj . gotoLine ( 0 , 0 ) ; // By moving, ace editor won't highlight anything
}
// This catches any change to the value including e.g. via backspace or paste.
@ -238,7 +236,7 @@ export class FormulaEditor extends NewBaseEditor {
if ( this . isDetached . get ( ) ) { return ; }
if ( errorDetails . get ( ) ) {
hideErrDetails . set ( ! hideErrDetails . get ( ) ) ;
this . _ formul aEditor. resize ( ) ;
this . _ ace Editor. resize ( ) ;
}
} ) ,
dom . maybe ( errorDetails , ( ) = >
@ -249,7 +247,7 @@ export class FormulaEditor extends NewBaseEditor {
if ( ! this . isDetached . get ( ) ) { return ; }
if ( errorDetails . get ( ) ) {
hideErrDetails . set ( ! hideErrDetails . get ( ) ) ;
this . _ formul aEditor. resize ( ) ;
this . _ ace Editor. resize ( ) ;
}
} )
) )
@ -281,9 +279,10 @@ export class FormulaEditor extends NewBaseEditor {
this . _editorPlacement = EditorPlacement . create (
this . _placementHolder , this . _dom , cellElem , { margins : getButtonMargins ( ) } ) ;
// Reposition the editor if needed for external reasons (in practice, window resize).
this . autoDispose ( this . _editorPlacement . onReposition . addListener ( this . _formulaEditor . resize , this . _formulaEditor ) ) ;
this . _formulaEditor . onAttach ( ) ;
this . _formulaEditor . resize ( ) ;
this . autoDispose ( this . _editorPlacement . onReposition . addListener ( this . _aceEditor . resize , this . _aceEditor ) ) ;
this . _aceEditor . onAttach ( ) ;
this . _updateEditorPlaceholder ( ) ;
this . _aceEditor . resize ( ) ;
this . focus ( ) ;
}
@ -292,33 +291,33 @@ export class FormulaEditor extends NewBaseEditor {
}
public setFormula ( formula : string ) {
this . _ formul aEditor. setValue ( formula ) ;
this . _ ace Editor. setValue ( formula ) ;
}
public getCellValue() {
const value = this . _ formul aEditor. getValue ( ) ;
const value = this . _ ace Editor. getValue ( ) ;
// Strip the leading "=" sign, if any, in case users think it should start the formula body (as
// it does in Excel, and because the equal sign is also used for formulas in Grist UI).
return ( value [ 0 ] === '=' ) ? value . slice ( 1 ) : value ;
}
public getTextValue() {
return this . _ formul aEditor. getValue ( ) ;
return this . _ ace Editor. getValue ( ) ;
}
public getCursorPos() {
const aceObj = this . _ formul aEditor. getEditor ( ) ;
const aceObj = this . _ ace Editor. getEditor ( ) ;
return aceObj . getSession ( ) . getDocument ( ) . positionToIndex ( aceObj . getCursorPosition ( ) ) ;
}
public focus() {
if ( this . isDisposed ( ) ) { return ; }
this . _ formul aEditor. getEditor ( ) . focus ( ) ;
this . _ ace Editor. getEditor ( ) . focus ( ) ;
}
public resize() {
if ( this . isDisposed ( ) ) { return ; }
this . _ formul aEditor. resize ( ) ;
this . _ ace Editor. resize ( ) ;
}
public detach() {
@ -331,25 +330,26 @@ export class FormulaEditor extends NewBaseEditor {
this . _placementHolder . clear ( ) ;
// We are going in the full formula edit mode right away.
this . options . editingFormula ( true ) ;
this . _updateEditorPlaceholder ( ) ;
// Set the focus in timeout, as the dom is added after this function.
setTimeout ( ( ) = > ! this . isDisposed ( ) && this . _ formul aEditor. resize ( ) , 0 ) ;
setTimeout ( ( ) = > ! this . isDisposed ( ) && this . _ ace Editor. resize ( ) , 0 ) ;
// Return the dom, it will be moved to the floating editor.
return this . _dom ;
}
private _updateEditorPlaceholder() {
const editor = this . _ formul aEditor. getEditor ( ) ;
const editor = this . _ ace Editor. getEditor ( ) ;
const shouldShowPlaceholder = editor . session . getValue ( ) . length === 0 ;
const placeholderNode = editor . renderer . emptyMessageNode ;
if ( placeholderNode ) {
if ( editor . renderer . emptyMessageNode ) {
// Remove the current placeholder if one is present.
editor . renderer . scroller . removeChild ( placeholder Node) ;
editor . renderer . scroller . removeChild ( editor. renderer . emptyMessage Node) ;
}
if ( ! shouldShowPlaceholder ) {
editor . renderer . emptyMessageNode = null ;
} else {
const withAiButton = this . _canDetach && ! this . isDetached . get ( ) && HAS_FORMULA_ASSISTANT ( ) ;
editor . renderer . emptyMessageNode = cssFormulaPlaceholder (
! this . _canDetach || this . isDetached . get ( )
! withAiButton
? t ( 'Enter formula.' )
: t ( 'Enter formula or {{button}}.' , {
button : cssUseAssistantButton (
@ -361,7 +361,6 @@ export class FormulaEditor extends NewBaseEditor {
) ;
editor . renderer . scroller . appendChild ( editor . renderer . emptyMessageNode ) ;
}
this . _formulaEditor . resize ( ) ;
}
private _handleUseAssistantButtonClick ( ev : MouseEvent ) {
@ -380,7 +379,7 @@ export class FormulaEditor extends NewBaseEditor {
} ;
}
const placeholder : HTMLElement | undefined = this . _ formul aEditor. getEditor ( ) . renderer . emptyMessageNode ;
const placeholder : HTMLElement | undefined = this . _ ace Editor. getEditor ( ) . renderer . emptyMessageNode ;
if ( placeholder ) {
// If we are showing the placeholder, fit it all on the same line.
return this . _editorPlacement . calcSizeWithPadding ( elem , {
@ -422,7 +421,7 @@ export class FormulaEditor extends NewBaseEditor {
const colId = col . origCol . peek ( ) . colId . peek ( ) ;
const aceObj = this . _ formul aEditor. getEditor ( ) ;
const aceObj = this . _ ace Editor. getEditor ( ) ;
// Rect only to columns in the same table.
if ( col . tableId . peek ( ) !== this . options . column . table . peek ( ) . tableId . peek ( ) ) {
@ -451,7 +450,7 @@ export class FormulaEditor extends NewBaseEditor {
// Else touching a normal identifier, don't mangle it
}
// Resize editor in case it is needed.
this . _ formul aEditor. resize ( ) ;
this . _ ace Editor. resize ( ) ;
// This focus method will try to focus a textarea immediately and again on setTimeout. But
// other things may happen by the setTimeout time, messing up focus. The reason the immediate