WorkspaceCompletionSupport.st
author Claus Gittinger <cg@exept.de>
Fri, 06 Mar 2020 21:56:26 +0100
changeset 6843 6c5e543e903e
parent 6705 efea30a3e8e0
permissions -rw-r--r--
#OTHER by cg class: SimpleView added: #isWarningBox

"
 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:'NumberOfCompletionSuggestionsShown'
	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 class methodsFor:'defaults'!

numberOfCompletionSuggestionsShown
    "/ used to be 25, but that makes a long list, which seems disturbung
    ^ NumberOfCompletionSuggestionsShown ? 15.

    "Created: / 15-07-2019 / 17:24:03 / Claus Gittinger"
! !

!WorkspaceCompletionSupport methodsFor:'private'!

computeCompletions
    "compute completions (but do not show them)"

    |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. 
    ^ {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"
    "Modified (comment): / 15-07-2019 / 17:34:27 / Claus Gittinger"
!

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

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

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

    completionView isNil ifTrue:[
        self openCompletionView:nil.
    ].

    "/ if the completionView has been closed in the meantime
    (v := completionView) isNil ifTrue: [
        ^ self
    ].

    implementations := implementationsArg.
    actions := actionsArg.

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

    numShown := self class numberOfCompletionSuggestionsShown.
    suggestions size > numShown ifTrue:[
        numFirst := numShown-3.    
        numLast := 3.
        numLast := 0.
        numSkipped := suggestions size-numShown.

        suggestions := (suggestions copyTo:numShown-numLast) 
                        , { ('<< %1 more skipped >>' bindWith:numSkipped) allGray }  
                        , (suggestions copyLast:numLast).
        implementations isSequenceable ifTrue:[ 
            implementations := (implementations copyTo:numShown-numLast),#(nil),(implementations copyLast:numLast).
        ] ifFalse:[
            self halt
        ].    
        actions isSequenceable ifTrue:[ 
            actions := (actions copyTo:numShown-numLast),#(nil),(actions copyLast:numLast).
        ] ifFalse:[
"/            actions isBlock ifTrue:[
"/                "/ the block will be called with the index of the selected completion;
"/                "/ this is now wrong, as we have changed the list!!.
"/                "/ wrap the block by an index-adjusting action
"/                actions := [:selectedIndex |
"/                                |adjustedIndex|
"/
"/                                selectedIndex <= numShown ifTrue:[
"/                                    adjustedIndex := selectedIndex
"/                                ] ifFalse:[
"/                                    adjustedIndex := selectedIndex + numSkipped.
"/                                ].  
"/                                actionsArg value:adjustedIndex.
"/                           ].     
"/            ] ifFalse:[
"/                self halt
"/            ].    
        ].    
    ].

    "/ append snippet, if any (can be easily reached via CRSR-up)
    suggestionOffsetDueToSnippets := 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)') allGray)).
                    indexOfSnippet := suggestions size.

                    "/ change below, when reversing the order in above code (i.e. when snippets come first)
                    "/ suggestionOffsetDueToSnippets := 2.
                ]
            ]
        ].
    ].

    suggestions isEmptyOrNil ifTrue:[
        self closeCompletionView.
        ^ self
    ].
    (v == completionView) ifFalse: [
        "/ the completionView has been closed in the meantime
        ^ self
    ].

    v sensor
        pushAction:[
            |top idx preselectIdx performCompletion|

            "/ if the completionView has not been closed in the meantime
            (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 | 
                        self closeCompletionView.
                        (selectedListIndex == indexOfSnippet) ifTrue:[
                            "/ replace by the sniplet
                            editView sensor pushUserEvent:#expandAbbreviation for:editView
                        ] ifFalse:[
                            |indexInSuggestions|

                            indexInSuggestions := selectedListIndex - suggestionOffsetDueToSnippets.
                            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:[
                        self adjustSizeOfCompletionView:top.
"/                        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"
    "Modified: / 15-07-2019 / 17:57:56 / Claus Gittinger"
! !

!WorkspaceCompletionSupport class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !