WorkspaceCompletionSupport.st
author Claus Gittinger <cg@exept.de>
Thu, 09 Nov 2017 20:09:30 +0100
changeset 6225 0122e4e6c587
parent 6224 051efa88ece4
child 6631 476a7030cebb
permissions -rw-r--r--
#FEATURE by cg class: GenericToolbarIconLibrary class added: #hideFilter16x16Icon

"
 COPYRIGHT (c) 2013 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libwidg' }"

"{ NameSpace: Smalltalk }"

EditTextViewCompletionSupport subclass:#WorkspaceCompletionSupport
	instanceVariableNames:''
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Smalltalk'
!

!WorkspaceCompletionSupport class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2013 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    A completion support using DWIM to complete code for Smalltalk (and JavaScript)

    [author:]
        Claus Gittinger

    [instance variables:]

    [class variables:]

    [see also:]
        DoWhatIMeanSupport

"
! !

!WorkspaceCompletionSupport methodsFor:'private'!

computeCompletions

    |topView suggestions implementations actions contextOrNil|

    "/ a hack - we get better completions, if we know the current context
    topView := editView topView.
    (topView notNil and:[topView isDebugView]) ifTrue:[
        contextOrNil := topView selectedContext.
    ].

    UserInformation ignoreIn:[
        DoWhatIMeanSupport new
            setSelf: (editView simulatedSelf);
            codeCompletionFor: editView codeAspect
            language: editView editedLanguage
            method:editView editedMethod
            orClass:editView editedClass 
            context:contextOrNil 
            codeView:editView 
            into:[:listOfSuggestions :listOfActions :titleWhenAsking |
                    "/ (listOfSuggestions contains:[:l | l isEmptyOrNil]) ifTrue:[self halt].
                    suggestions := listOfSuggestions collect:[:entry | entry isArray ifTrue:[entry first] ifFalse:[entry]].
                    implementations := listOfSuggestions collect:[:entry | entry isArray ifTrue:[entry second] ifFalse:[nil]].                            
                    actions := listOfActions.
                    nil "/ must return nil to avoid DWIM to do it itself (for now)
            ]
    ].
    "/ Transcript show:'suggestions: '; showCR:suggestions.
    "/ Transcript show:'actions: '; showCR:actions. 
    editView sensor
        pushUserEvent:#'suggestionsArrived:implementations:actions:autoSelect:'
        for:self
        withArguments:{suggestions . implementations . actions . autoSelect }

    "Created: / 26-09-2013 / 17:44:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 09-03-2017 / 10:48:44 / cg"
!

suggestionsArrived:suggestionsArg implementations:implementationsArg actions:actionsArg autoSelect:autoSelectArg
    "the background process has generated some suggestions"

    |v numShown numFirst numLast numSkipped
     suggestions implementations actions suggestionOffset keyAndSnippet indexOfSnippet|

    (editView sensor hasKeyPressEventFor:nil) ifTrue:[ 
        self closeCompletionView. 
        ^ self
    ].

    implementations := implementationsArg.
    actions := actionsArg.

    suggestions := suggestionsArg ? #().
    suggestions := suggestions reject:[:el | el isNil].  

    numShown := 25.
    suggestions size > numShown ifTrue:[
        numFirst := numShown-5.    
        numLast := 5.
        numSkipped := suggestions size-numShown.    
        suggestions := (suggestions copyTo:numShown-5) 
                        , { ('<< %1 more skipped >>' bindWith:numSkipped) withColor:Color grey }  
                        , (suggestions copyLast:5).
        implementations isArray ifTrue:[ 
            implementations := (implementations copyTo:numShown-5),#(nil),(implementations copyLast:5).
        ].
        actions isArray ifTrue:[ 
            actions := (actions copyTo:numShown-5),#(nil),(actions copyLast:5).
        ].
    ].

    "/ append snipplet, if any (can be easily reached via CRSR-up)
    suggestionOffset := 0.
    indexOfSnippet := nil.
    UserPreferences current appendAbbreviationsToCompletionSuggestions ifTrue:[
        (keyAndSnippet := editView findAbbreviationKeyBeforeCursor) notNil ifTrue:[
            |abbrev sniplet i line|

            abbrev := keyAndSnippet first.
            sniplet := keyAndSnippet second.

            "/ if the abbreviation is simply at the end of a longer word, ignore the abbrev.
            line := editView lineStringBeforeCursor.
            i := line findLast:[:ch | ch isLetterOrDigit not].
            (i < (line size - abbrev size - 1)) ifFalse:[
                sniplet := sniplet copyWithout:$!!.

                "/ true, false and self are often found in both lists
                (suggestions includes:sniplet) ifFalse:[   
                    suggestions isEmpty ifFalse:[ suggestions := suggestions copyWith: '-' ]. 
                    suggestions := suggestions copyWith: ( '%1 %2'
                                            bindWith:(sniplet asStringCollection first "contractTo:25")
                                            with: ( ('("',abbrev,'" snippet)') withColor:Color gray)).
                    indexOfSnippet := suggestions size.

                    "/ change below, when reversing the order in above code
                    "/ suggestionOffset := 2.
                ]
            ]
        ].
    ].

    suggestions isEmptyOrNil ifTrue:[
        self closeCompletionView.
        ^ self
    ].
    (v := completionView) isNil ifTrue: [
        ^ self
    ].

    v sensor
        pushUserEvent:#value
        for:[
            |top idx preselectIdx performCompletion|

            (v == completionView) ifTrue: [
                top := v topView.
                autoSelectArg ifTrue:[
                    LastCompletions notNil ifTrue:[
                        "/ one of the last completions in list?
                        idx := LastCompletions findFirst:[:compl | suggestions includes:compl].
                        idx ~~ 0 ifTrue:[
                            preselectIdx := suggestions indexOf:(LastCompletions at:idx).
                        ].
                    ].
                    (preselectIdx isNil and:[suggestions size == 1]) ifTrue:[
                        preselectIdx := 1.
                    ].
                ].
                preselectIdx notNil ifTrue:[
                    |pref|

                    pref := suggestions at:preselectIdx.
                    pref notNil ifTrue:[
                        "/ for now, do not move to front (action needs the index)
                        suggestions at:preselectIdx put:(pref allBold).
"/                    suggestions removeAtIndex:preselectIdx.                    
"/                    suggestions addFirst:(pref allBold).
"/                    implementations notNil ifTrue:[
"/                        implementations removeAtIndex:preselectIdx.
"/                        implementations addFirst:implementations.
"/                    ]
                    ].
                ].

                performCompletion :=
                    [:selectedListIndex | 
                        |indexInSuggestions|

                        self closeCompletionView.
                        indexInSuggestions := selectedListIndex - suggestionOffset.
                        (selectedListIndex == indexOfSnippet) ifTrue:[
                            "/ replace the sniplet
                            editView sensor pushUserEvent:#expandAbbreviation for:editView
                        ] ifFalse:[
                            LastCompletions isNil ifTrue:[
                                LastCompletions := OrderedCollection new.
                            ].
                            LastCompletions add:(suggestions at:indexInSuggestions).
                            LastCompletions size > 200 ifTrue:[
                                LastCompletions removeLast
                            ].
                            
                            actions notNil ifTrue:[
                                actions isBlock ifTrue:[
                                    (numFirst notNil and:[indexInSuggestions > numFirst]) ifTrue:[
                                        indexInSuggestions := indexInSuggestions + numSkipped - 1.
                                    ].    
                                    actions value:indexInSuggestions
                                ] ifFalse:[
                                    (actions at:indexInSuggestions) valueWithOptionalArgument:indexInSuggestions
                                ].
                            ].
                        ].
                        "/ disabled - user has made his choice; so don't show more suggestions
                        "/ editView sensor pushUserEvent:#updateCompletionList for:self
                    ].

                (autoSelectArg 
                    and:[ (suggestions size == 1) 
                    and:[ preselectIdx == 1
                    and:[ preselectIdx ~~ indexOfSnippet ]]]) ifTrue:[
                    "/ do it, right here and now
                    performCompletion value:preselectIdx.
                ] ifFalse:[
                    top open.
                    v list:suggestions 
                            expandTabs:false scanForNonStrings:false
                            includesNonStrings:false redraw:true.

                    implementations notNil ifTrue:[
                        implementations keysAndValuesDo:[:idx :impls |
                            |implsMenu|

                            impls notEmptyOrNil ifTrue:[
                                implsMenu := Menu new.
                                impls do:[:each |
                                    implsMenu addItem:(MenuItem new label:each name).
                                ].
                                v subMenuAt:idx put:implsMenu
                            ].
                        ].
                    ].

                    v enable:true.
                    preselectIdx notNil ifTrue:[
                        "/ very disturbing!!
                        v selection:preselectIdx.
                    ].
                    v extent:completionView preferredExtentForContents.
                    v action:performCompletion.

                    (top ~~ v) ifTrue:[
                        top resizeToFit.
                        top bottom > v device usableHeight ifTrue:[
                            top origin:((top origin x) @ (v device usableHeight - v height)).
                        ].
                        top raise.
                    ]
                ]
            ]
        ]

    "Modified: / 05-11-2017 / 11:10:47 / cg"
! !

!WorkspaceCompletionSupport class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !