SmallSense__CompletionController.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Tue, 24 Jun 2014 11:42:58 +0100
changeset 245 172822a63cff
parent 241 174331ea79a5
child 249 8bc64027b189
permissions -rw-r--r--
Fix in CompletionController>>prefixAlreadyWritten - let the first PO answer the prefix written... ...but only if all PO's are of the same class. This is hack rather than solution, the completer should store the prefix in a context.

"{ Package: 'jv:smallsense' }"

"{ NameSpace: SmallSense }"

EditTextViewCompletionSupport subclass:#CompletionController
	instanceVariableNames:'support seqno completeIfUnambiguous'
	classVariableNames:''
	poolDictionaries:''
	category:'SmallSense-Core'
!


!CompletionController class methodsFor:'instance creation'!

new
    "return an initialized instance"

    ^ self basicNew initialize.
! !

!CompletionController methodsFor:'accessing'!

completionEngine
    | engineClass |

    engineClass := self completionEngineClass.
    ^ engineClass notNil 
        ifTrue:[ engineClass new ]
        ifFalse:[ nil ].

    "Created: / 18-05-2014 / 11:58:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

environment
    ^ support notNil 
        ifTrue:[support environment]
        ifFalse:[Smalltalk].

    "Created: / 18-05-2014 / 11:53:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

support
    ^ support
!

support:anEditSupport
    support := anEditSupport.
! !

!CompletionController methodsFor:'accessing-classes'!

completionEngineClass
    ^ support notNil 
        ifTrue:[ support completionEngineClass ]
        ifFalse:[ nil ].

    "Created: / 18-05-2014 / 11:55:26 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CompletionController methodsFor:'events'!

handleKeyPress:key x:x y:y

    key == #Control_L ifTrue:[
        completionView notNil ifTrue:[
            ^ false.
        ].
    ].

    key == #CodeCompletion  ifTrue: [
        autoSelect := true.    
        self startCompletionProcess.
        ^ true
    ].

    (key == #BackSpace or:[key == #BasicBackspace]) ifTrue:[
        | c |

        c := editView characterBeforeCursor.
        (c notNil and:[c isAlphaNumeric]) ifTrue:[
             ^ false
        ].
    ].     


    completionView notNil ifTrue:[
        (key == #Return and:[completionView hasSelection]) ifTrue:[
            self complete.
            ^ true.
        ].
        key == #Tab ifTrue:[ 
            self handleKeyPressTab.  
            ^ true
        ].
        key isCharacter ifTrue:[
            (self updateSelectionAfterKeyPress: key) ifTrue:[ 
                ^ true
            ].
        ].
    ].
    ^ super handleKeyPress:key x:x y:y

    "Created: / 27-09-2013 / 15:38:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 18-06-2014 / 10:17:10 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

handleKeyPressTab
    "Tab has been pressed, try to complete longest common prefix"

    | first prefix matching longest minlen |

    first := completionView list first.
    prefix := self prefixAlreadyWritten.
    matching := OrderedCollection new.
    minlen := SmallInteger maxVal.
    completionView list do:[:po |
        | s |

        s := po stringToComplete.
        (s startsWith: prefix) ifTrue:[
            matching add: po -> s.
            minlen := minlen min: s size.
        ].
    ].
    matching isEmpty ifTrue:[
        completionView flash.
        ^self.
    ].
    matching size == 1 ifTrue:[
        self complete: matching first key.
    ].

    longest := String streamContents:[:s |
        | i |

        s nextPutAll: prefix.
        i := prefix size + 1.
        [ i <= minlen ] whileTrue:[
            | c |

            c := matching first value at: i.
            (matching allSatisfy:[:e|(e value at: i) == c]) ifTrue:[
                s nextPut:c.
                i := i + 1.
            ] ifFalse:[
                "/ terminate the loop    
                i := minlen + 2.
            ]
        ]
    ].
    longest size = prefix size ifTrue:[
        completionView flash.
        ^self.
    ].
    editView insertStringAtCursor:(longest copyFrom: prefix size + 1).

    "Created: / 31-03-2014 / 22:55:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 18-05-2014 / 13:55:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

postKeyPress:key
    seqno := seqno + 1.
    seqno == SmallInteger maxVal ifTrue:[
        seqno := 0.
    ].

    UserPreferences current immediateCodeCompletion ifFalse:[
        "/ only update, if already open
        completionView isNil ifTrue:[^ self].
    ].

"/    (key == #BackSpace or:[key == #BasicBackspace]) ifTrue:[
"/        self closeCompletionView.
"/        ^ self
"/    ].

    key isCharacter ifTrue:[
        key isLetterOrDigit not ifTrue:[
            "/ Hack for Java - should be delegated to completion engine    
            (key == $. and:[support notNil and:[ support language isJavaLike ]]) ifTrue:[ 
                ^ self
            ].
            self closeCompletionView
        ] ifFalse:[
            | c |

            c := editView characterBeforeCursor.
            (c notNil and:[c isLetterOrDigit]) ifTrue:[
                c := editView characterUnderCursor.
                c isSeparator ifTrue:[
                    autoSelect := false.
                    self updateCompletionList.
                ].
            ]
        ].
        ^ self
    ].

    "Created: / 28-09-2013 / 00:21:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 18-05-2014 / 13:53:18 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CompletionController methodsFor:'initialization'!

initialize
    "Invoked when a new instance is created."

    "/ please change as required (and remove this comment)
    "/ support := nil.
    seqno := 0.
    completeIfUnambiguous := UserPreferences current smallSenseCompleteIfUnambiguous.

    "/ super initialize.   -- commented since inherited method does nothing

    "Modified: / 18-01-2014 / 23:10:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CompletionController methodsFor:'private'!

complete
    self complete: completionView selection.

    "Created: / 27-09-2013 / 15:38:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 31-03-2014 / 23:22:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

complete: item
    self closeCompletionView.
    item insert

    "Created: / 31-03-2014 / 23:21:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

prefixAlreadyWritten
    | list first |

    completionView notNil ifTrue:[ 
        list := completionView list.
        list notEmptyOrNil ifTrue:[ 
            first := list first.
            (completionView list allSatisfy:[:e | e class == first class ]) ifTrue:[ 
                ^ first stringAlreadyWritten 
            ]
        ]
    ].
    ^ support wordBeforeCursor string .

    "Created: / 18-05-2014 / 13:55:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 24-06-2014 / 11:40:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

stopCompletionProcess
    "kill any background completion process"

    editView sensor flushUserEventsFor: self.     
    super stopCompletionProcess

    "Created: / 02-10-2013 / 15:09:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 03-10-2013 / 11:03:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

updateCompletionList
    "called for keypress events"

    completionView isNil ifTrue:[
        super updateCompletionList
    ] ifFalse:[
         self updateSelection.
    ].

    "Created: / 27-09-2013 / 15:58:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 28-09-2013 / 00:15:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

updateSelection
    "Updates selection in completion view based on currently typed partial 
     text. Return true if the complection window should be closed or false
     if it shall be kept open. "

    ^ self updateSelectionAfterKeyPress: nil

    "Created: / 27-09-2013 / 16:16:18 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 17-06-2014 / 07:24:08 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

updateSelectionAfterKeyPress: keyOrNil
    "Updates selection in completion view based on currently typed partial 
     text. Return true if the complection window should be closed or false
     if it shall be kept open.

     If `keyOrNil` is not nil, then it's a key press that triggered the update
     which HAS NOT YET been processed by a `editView`.
     "

    | list prefix matcher1 matches1 matcher2 matches2 |

    list := completionView list.
    matcher1 := CompletionEngine exactMatcher.
    matcher2 := CompletionEngine inexactMatcher.
    prefix := self prefixAlreadyWritten.
    keyOrNil isCharacter ifTrue:[ 
        prefix := prefix , keyOrNil
    ].
    matches1 := list select:[:po | matcher1 value: prefix value: po stringToComplete ].
    matches1 notEmptyOrNil ifTrue:[
        matches1 size == 1 ifTrue:[
            completionView selection:  matches1 anElement.
            completeIfUnambiguous ifTrue:[
                self complete.
                ^ true
            ].
        ] ifFalse:[
            | selection |

            selection := matches1 inject: matches1 anElement into:[:mostrelevant :each |
                each relevance > mostrelevant relevance 
                    ifTrue:[each]
                    ifFalse:[mostrelevant]
            ].
            completionView selection: selection.
        ].
        ^ false
    ].

    matches2 := completionView list select:[:po | matcher2 value: prefix value: po stringToComplete ].
    matches2 notEmptyOrNil ifTrue:[
        matches2 size == 1 ifTrue:[
            completionView selection:  matches2 anElement.
        ] ifFalse:[
            | selection |

            selection := matches2 inject: matches2 anElement into:[:mostrelevant :each |
                each relevance > mostrelevant relevance 
                    ifTrue:[each]
                    ifFalse:[mostrelevant]
            ].
            completionView selection: selection.
        ]
    ] ifFalse:[
        completionView selection: nil.
    ].
    ^ false.

    "Created: / 17-06-2014 / 07:19:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 24-06-2014 / 11:41:14 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CompletionController methodsFor:'private-API'!

closeCompletionView
    |v|

    self stopCompletionProcess.
    (v := completionView) notNil ifTrue:[
        completionView := nil.
        "/ let it close itself - avoids synchronization problems
        v sensor
            pushUserEvent:#value
            for:[ v topView destroy ].
    ].

    "Created: / 02-10-2013 / 13:57:27 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-10-2013 / 21:14:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

computeCompletions
    "Actually compute the completions and update the completion view."  

    | completions |

    editView sensor flushUserEventsFor: self.

    "/ Wait a while to give user chance finish typing.
    "/ This also reduces CPU consumption by avoiding
    "/ useless computation
    Delay waitForMilliseconds: 200. 

    completions := self computeCompletionsInContext.
    completions notEmptyOrNil ifTrue:[
        editView sensor pushUserEvent: #updateCompletions:sequence: for: self withArguments: (Array with: completions with: seqno)
    ].

    "Created: / 27-09-2013 / 13:12:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 18-05-2014 / 11:50:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

computeCompletionsInContext
    | context |

    context := CompletionContext new.
    context environment: self environment.
    context support: support.
    ^self computeCompletionsInContext: context.

    "Created: / 18-05-2014 / 11:50:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

computeCompletionsInContext: aCompletionContext
    | engine |

    engine := self completionEngine.
    ^engine notNil 
        ifTrue:[ engine complete: aCompletionContext ]
        ifFalse:[ nil ]

    "Created: / 18-05-2014 / 11:53:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

openCompletionView
    self openCompletionView: #()

    "Created: / 27-09-2013 / 16:17:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

openCompletionView: list
    "Makes sure the completion view is opened and with given `list`."
    
    | movePos topView x y  windowExtent screenExtent |
    "/ move the window

    list isEmpty ifTrue:[ ^ self ].
    list = #( 'Busy...' ) ifTrue:[ ^ self ].  

    x := (editView xOfCol:editView cursorCol  inVisibleLine:editView cursorLine)
            - 16"icon" - (editView widthOfString:  "support wordBeforeCursor"list first stringAlreadyWritten) - 5"magic constant".
    y := editView yOfCursor + editView font maxHeight + 3.
    movePos := (editView originRelativeTo: nil) + (x @ y).

    completionView isNil ifTrue:[

        completionView := CompletionView new.
        completionView completionController: self.  
        completionView list:list.
        completionView font: editView font.
        topView := completionView.

        windowExtent := completionView extent copy.
        screenExtent := Screen current monitorBoundsAt: movePos.
        (screenExtent height) < (movePos y + windowExtent y) ifTrue:[
            movePos y: (movePos y - windowExtent y - editView font maxHeight - 5).
        ].
        topView origin:movePos.
"/        topView resizeToFit.
        self updateSelection ifFalse:[
            topView open.
        ].
    ] ifFalse:[
        completionView list:list.
        self updateSelection.
"/        topView := completionView topView.
"/        topView ~~ completionView ifTrue:[
"/            topView origin:movePos.
"/            topView resizeToFit.
"/        ]
    ].

    "Created: / 27-09-2013 / 14:01:27 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 15-05-2014 / 11:30:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

updateCompletions: completionResult sequence: sequence
    seqno == sequence ifTrue:[
        self openCompletionView: completionResult 
    ].

    "Created: / 03-10-2013 / 07:14:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 03-10-2013 / 11:02:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CompletionController class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
! !