Workspace.st
author Claus Gittinger <cg@exept.de>
Wed, 22 Sep 2010 14:22:56 +0200
changeset 4170 6bf83a2af71f
parent 4158 502e6898c82d
child 4171 20a315cd98c4
permissions -rw-r--r--
changed: #error:position:to:from:asWarning: do not raise a warning signal

"
 COPYRIGHT (c) 1989 by Claus Gittinger
	      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' }"

TextCollector subclass:#Workspace
	instanceVariableNames:'doItAction codeStartPosition errorFgColor errorBgColor
		commentStrings autoDefineWorkspaceVariables simulatedSelf
		autoDefineVariables compilerClass allowValueDrop
		poolsConsideredInDoIts'
	classVariableNames:'DefaultViewBackground DefaultErrorForegroundColor
		DefaultErrorBackgroundColor DefaultWarningBackgroundColor
		DefaultWarningForegroundColor WorkspaceVariables DoItHistory
		Sniplets'
	poolDictionaries:''
	category:'Interface-Smalltalk'
!

Workspace comment:'declared from: ..\..\..\stx\libwidg\abbrev.stc'
!

!Workspace class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1989 by Claus Gittinger
	      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 view for editable text which can evaluate expressions.
    I.e. its basically a view for editable text, with added
    'doIt', 'printIt' and 'inspectIt' functions on the popup-menu.

    The action to be performed on doIt is defined by a block,
    which can be defined by the owner of this view.
    (thus you can put a workspace into more complex widgets, and
     control what should happen on 'doIt').

    A useful default action is automatically defined, which simply 
    evaluates the selection as a smalltalk expression. 
    (but, a lisp or prolog workspace would define its own action,
     to call for another compiler/interpreter  ...)


    Caveat:
        in this version, Workspace does not yet support doIt in MVC setups.
        For now, simulate this by setting the doItAction, to notify the
        model manually about the doIt.


    [instance variables:]

      doItAction      <Block>         block to evaluate for doIt

      errorFgColor    <Color>         fg-Color to be used when highlighting errors 

      errorBgColor    <Color>         bg-Color to be used when highlighting errors

      codeStartPosition               private temporary


    [styleSheet values:]

      codeErrorSelectionForegroundColor     fg color to highlight errors
                                            (default: selection fg)

      codeErrorSelectionBackgroundColor     bg color to highlight errors
                                            (default: selection bg)

    [start with:]
        Workspace open

    [see also:]
        Workspace EditTextView 
        Parser ByteCodeCompiler

    [author:]
        Claus Gittinger
"
! !

!Workspace class methodsFor:'accessing'!

sniplets
    Sniplets isNil ifTrue:[
        Sniplets := Dictionary new.
        self initializeDefaultAbbreviations.
    ].
    ^ Sniplets

    "
     Sniplets := nil
    "
!

sniplets:something
    Sniplets := something.
! !

!Workspace class methodsFor:'defaults'!

defaultLabel
    "my default window label"

    ^ 'Workspace'

    "Created: / 16.5.1998 / 16:53:37 / cg"
!

initializeDefaultAbbreviations
    "default sniplets/abbreviations. TODO: save/load sniplets"

    "flush and reinitialize sniplets with:
         Sniplets := Dictionary new.
    "
    "after a code change below, update with:
         self initializeDefaultAbbreviations.
    "

    #(
        't'     'true'    
        'f'     'false'
        's'     'self'
        'su'    'super'                  
        'ss'    'super '            
        'n'     'nil'
        'y'     'yourself.'

        'in'    'isNil '
        'nn'    'notNil '
        'ie'    'isEmpty '
        'ne'    'notEmpty '
        'ien'   'isEmptyOrNil '
        'nen'   'notEmptyOrNil '

        'it'    'ifTrue:[!!]'
        'if'    'ifFalse:[!!]'
        'itf'   'ifTrue:[!!] ifFalse:[]'
        'int'   'isNil ifTrue:[!!]'
        'inf'   'isNil ifFalse:[!!]'
        'ints'  'isNil ifTrue:[^ self]'
        'infs'  'isNil ifFalse:[^ self]'
        'nnt'   'notNil ifTrue:[!!]'
        'nnf'   'notNil ifFalse:[!!]'

        'wt'    'whileTrue:[!!]'
        'wf'    'whileFalse:[!!]'

        'do'    'do:[:each | !!]'
        'kdo'   'keysAndValuesDo:[:each | !!]'
        'dt'    'detect:[:each | !!]'   
        'de'    'detect:[:each | !!]'
        'det'   'detect:[:each | !!]'
        'dtn'   'detect:[:each | !!] ifNone:[]'
        'cl'    'collect:[:each | !!]'
        'co'    'collect:[:each | !!]'
        'col'   'collect:[:each | !!]'
        'sl'    'select:[:each | !!]'
        'se'    'select:[:each | !!]'
        'sel'   'select:[:each | !!]'
        'rj'    'reject:[:each | !!]'
        're'    'reject:[:each | !!]'
        'rej'   'reject:[:each | !!]'
        'inj'   'inject:!! into:[:accum :each | ]'

        'ex'    'Error handle:[ex | !!] do:[]'
        'sh'    'self halt.'
        'mt'    'MessageTally spyOn:[\!!\]'

        'ik'    'includesKey: #'
        'is'    'includesString: #'

        'af'    'asFilename '
        'as'    'asString '
        'aoc'   'asOrderedCollection '

        'np'    'nextPut: '
        'npa'   'nextPutAll: '
        'npl'   'nextPutLine: '

        'ps'    'printString'
        'sr'    'self subclassResponsibility'

        'ati'   'at:!! ifAbsent: '
        'atip'  'at:!! ifAbsentPut:[ ] '
        'ap'    'at:!! '

        'st'    'Smalltalk'
        'ts'    'Transcript showCR:''!!'''
        'trs'   'Transcript showCR:''!!'''
        'abb'   'Workspace sniplets inspect'
        'ws'    'Delay waitForSeconds: 1' 
        'wfs'   'Delay waitForSeconds: 1' 
        'wfm'   'Delay waitForMilliseconds: 1000' 
        'ini'   'initialize\    super initialize.\    '
        'newi'  'new\    ^ super new initialize.'
        'upd'   'update:something with:aParameter from:changedObject\    !!\    ^ super update:something with:aParameter from:changedObject.'
        'OC'    'OrderedCollection'
        'oc'    'OrderedCollection'
        'SC'    'SortedCollection'
        'sc'    'SortedCollection'
        'D'     'Dictionary'
        'ID'    'IdentityDictionary'
        'Id'    'IdentityDictionary'
        'id'    'IdentityDictionary'
        'iD'    'IdentityDictionary'
        'OCn'   'OrderedCollection new.'
        'ocn'   'OrderedCollection new.'
        'SCn'   'SortedCollection new.'
        'IDn'   'IdentityDictionary new'
        'idn'   'IdentityDictionary new'
        'Dn'    'Dictionary new'
        'dn'    'Dictionary new'
        'Sn'    'Set new'
        'sn'    'Set new'
        'A'     'Array'
        'a'     'Array'
        'An'    'Array new:'
        'an'    'Array new:'
        'Aw'    'Array with:'
        'aw'    'Array with:'
        'Aww'   'Array with:!! with:'
        'sww'   'Array with:!! with:'
        'Awww'  'Array with:!! with: with:'
        'awww'  'Array with:!! with: with:'
        'Awwww' 'Array with:!! with: with: with:'
        'awwww' 'Array with:!! with: with: with:'
        'aw2'   'Array with:!! with:'
        'aw3'   'Array with:!! with: with:'
        'aw4'   'Array with:!! with: with: with:'
        '0'     '(0.0 @ 0.0)'
        '1'     '(1.0 @ 1.0)'
        '['     '[:!! ]'
        '('     '(!! )'                                    

        "/ typos...
        'eslf'  'self'
        'slef'  'self'
        'sefl'  'self'
        'elf'   'self'
        'slf'   'self'
        'sef'   'self'

        'iftrue'   'ifTrue'
        'iffalse'  'ifFalse'
        'iftrue:'  'ifTrue:'
        'iffalse:' 'ifFalse:'
    ) pairWiseDo:[:abbrev :text |
        Sniplets
            at:abbrev put:text "/ ifPresent:[ self error:'duplicate abbreviation key' ]
    ].

    "Modified: / 06-08-2010 / 11:10:34 / cg"
!

updateStyleCache
    "extract values from the styleSheet and cache them in class variables"

    <resource: #style (#'codeErrorSelection.foregroundColor'
                       #'codeErrorSelection.backgroundColor'
                       #'codeView.backgroundColor' )>

    DefaultErrorForegroundColor := StyleSheet colorAt:'codeErrorSelection.foregroundColor'.
    DefaultErrorBackgroundColor := StyleSheet colorAt:'codeErrorSelection.backgroundColor'.
    DefaultViewBackground := StyleSheet colorAt:'codeView.backgroundColor'.
! !

!Workspace class methodsFor:'getting a new Workspace'!

open
    "launch a new workspace"

    |scr topView workspace f|

    topView := StandardSystemView 
                label:(self classResources string:(self defaultLabel)) 
                " minExtent:(100 @ 100)".

    scr := HVScrollableView for:self in:topView.
    scr origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
    workspace := scr scrolledView.

    "/ adjust topViews extent according to my font

    f := workspace font.
    topView extent:((f widthOf:'x') * 40) @ (f height * 10).
    topView open.
    ^ workspace

    "
     Workspace open
    "

    "Modified: / 16.5.1998 / 16:53:53 / cg"
!

openForRemote:hostName
    "launch a new workspace to evaluate expression on some remote machine.
     Entered expressions are sent over to some partner machine, evaluated there,
     and the result is shown here.
     This requires the RemoteObjects package to be loaded."

    |server remoteCompiler workspace|

    RemoteObjectServer isNil ifTrue:[
        self warn:'no remoteObjectServer available'.
        ^ nil
    ].

    server := RemoteObjectServer on:hostName.
    remoteCompiler := server get:#Compiler.

    workspace := self open.
    workspace topView 
        label:(self classResources string:'Remote Workspace {%1}' with:hostName).

    workspace doItAction:
                [:theCode |
                    remoteCompiler 
                        evaluate:theCode 
                        in:nil 
                        receiver:nil 
                        notifying:workspace 
                        logged:true 
                        ifFail:nil 
                ]
    "
     Workspace openForRemote:'andi'
    "

    "Modified: / 16.5.1998 / 16:57:38 / cg"
!

openWith:initialText selected:selectedBoolean
    "launch a new workspace with some initial contents"

    |workspace|

    workspace := self open.
    workspace contents:initialText selected:selectedBoolean.
    ^ workspace

    "
     Workspace openWith:'Transcript showCR:''hello world'''
    "
! !

!Workspace class methodsFor:'history'!

clearDoItHistory
    DoItHistory := nil
!

doItHistory
    ^ DoItHistory
!

doItHistorySize
    "the number of remembered doIts"

    ^ 20
!

rememberDoIt:aString
    |string|

    string := aString asString string withoutSeparators.
    (string asCollectionOfWords size <= 1) ifTrue:[
        ((Scanner new scanTokens:string) size <= 1) ifTrue:[
            "its a variable only"
            ^ self
        ]
    ].

    DoItHistory isNil ifTrue:[
        DoItHistory := OrderedCollection new.
    ].
    DoItHistory remove:string ifAbsent:nil.
    DoItHistory addFirst:string.
    DoItHistory size > self doItHistorySize ifTrue:[
        DoItHistory removeLast
    ].

    Scanner
! !

!Workspace class methodsFor:'queries'!

isVisualStartable
    "returns whether this application class can be started via #open
     (i.e. via a double click on the class in the browser)"

    ^ self == Workspace

    "Created: / 16.5.1998 / 16:59:00 / cg"
    "Modified: / 16.5.1998 / 16:59:39 / cg"
! !

!Workspace class methodsFor:'workspace variables'!

addWorkspaceVariable:name
    "create a new workspace variable"

    |holder|

    holder := self workspaceVariables at:name ifAbsentPut:[ ValueHolder new ].
    ^ holder
!

anyWorkspaceVariableIsDefined
    ^ WorkspaceVariables notEmptyOrNil

    "Created: / 20-04-2005 / 11:57:53 / cg"
!

removeAllWorkspaceVariables
    "delete all workspace variables"

    WorkspaceVariables := nil


!

removeWorkspaceVariable:name
    "delete a workspace variable"

    WorkspaceVariables notNil ifTrue:[
        WorkspaceVariables removeKey:name ifAbsent:nil.
        WorkspaceVariables isEmpty ifTrue:[
            WorkspaceVariables := nil
        ]
    ].


!

workspaceVariableAt:name
    "retrieve a workspace variable (actually, a holder onto it)"

    WorkspaceVariables isNil ifTrue:[^ nil].
    ^ WorkspaceVariables at:name ifAbsent:nil.


!

workspaceVariableNames
    "retrieve the collection of workspace variable names"

    WorkspaceVariables isNil ifTrue:[^ #()].
    ^ WorkspaceVariables keys

    "Created: / 20-04-2005 / 11:42:45 / cg"
!

workspaceVariables
    "retrieve the collection of workspace variables.
     That is a dictionary associating names to values."

    WorkspaceVariables isNil ifTrue:[
        WorkspaceVariables := Dictionary new.
    ].
    ^ WorkspaceVariables

    "Modified: / 20-04-2005 / 11:43:14 / cg"
! !

!Workspace methodsFor:'accessing'!

allowValueDrop:aBoolean
    "if on (the default), any smalltalk value can be dropped and leads to a workspace variable
     holding on to that being defined. Can be turned off, if youdont like this (for standAlone apps)"

    allowValueDrop := aBoolean.

    "Created: / 28-11-2006 / 16:13:02 / cg"
!

autoDefineVariables
    "undefined variables handling:
        are automatically defined as workspace variable if autoDefineVariables is #workspace.
        are automatically defined as doit variable if autoDefineVariables is #doit.
        are left undefined if autoDefineVariables is nil."

    ^ autoDefineVariables 
!

autoDefineVariables:nilOrSymbol
    "undefined variables handling:
        are automatically defined as workspace variable if nilOrSymbol is #workspace.
        are automatically defined as doit variable if nilOrSymbol is #doit.
        are left undefined if nilOrSymbol is nil."

    autoDefineVariables := nilOrSymbol.

    "Modified: / 28-11-2006 / 16:21:01 / cg"
!

commentStrings:anArrayOfCommentStrings
    "define the comment strings"

    commentStrings := anArrayOfCommentStrings

    "Created: / 9.11.1997 / 01:05:25 / cg"
!

doItAction
    "return the action to be performed when 'doIt' is selected"

    ^ doItAction
!

errorBackgroundColor
    errorBgColor notNil ifTrue:[ ^ errorBgColor ].
    DefaultErrorBackgroundColor notNil ifTrue:[ ^ DefaultErrorBackgroundColor ].
    device hasColors ifTrue:[ ^ Color red ].

    ^ selectionBgColor
!

errorForegroundColor
    errorFgColor notNil ifTrue:[ ^ errorFgColor ].
    DefaultErrorForegroundColor notNil ifTrue:[ ^ DefaultErrorForegroundColor ].
    ^ selectionFgColor
!

hasSelectionOrTextInCursorLine
    ^ (self selectionOrTextOfCursorLine:false) notNil
!

poolsConsideredInDoIts:something
    poolsConsideredInDoIts := something.
!

selectionOrTextOfCursorLine
    ^ self selectionOrTextOfCursorLine:true
!

selectionOrTextOfCursorLine:doSelect
    |sel lNr line|

    sel := self selectionAsString.
    sel notNil ifTrue:[^ sel].

    lNr := self cursorLine.
    line := self listAt:lNr.
    line notEmptyOrNil ifTrue:[
        doSelect ifTrue:[
            self selectLine:lNr.
        ].
        ^ line
    ].

    ^ nil
!

simulatedSelf:anObject
    "define what self is in an evaluation"

    simulatedSelf := anObject
!

warningBackgroundColor
    DefaultWarningBackgroundColor notNil ifTrue:[ ^ DefaultWarningBackgroundColor ].
    device hasColors ifTrue:[ ^ Color orange ].

    ^ selectionBgColor
!

warningForegroundColor
    DefaultWarningForegroundColor notNil ifTrue:[ ^ DefaultWarningForegroundColor ].
    ^ selectionFgColor
! !

!Workspace methodsFor:'compiler interface'!

compilerClass
    ^ compilerClass ? Compiler
!

compilerClass:aCompilerClass
    compilerClass := aCompilerClass
!

currentSourceCode
    "special interface to compiler - called by parser
     to get the updated source code after a corrected error"

    ^ self contents
!

wantChangeLog
    "sent by the compiler to ask if a changeLog entry should
     be written. Return true here."

    ^ true
! !

!Workspace methodsFor:'compiler interface-error handling'!

correctableError:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifies us of a correctable error;
     hilight the error (relPos to relEndPos) and show a Box asking for continue/correct/abort;
     this method should return true to the compiler if user wants the error
     to be corrected; false otherwise"

    |action|

    self highlightingErrorPosition:relPos to:relEndPos do:[
        Dialog aboutToOpenBoxNotificationSignal handle:[:ex |
            |box declareButton|

            box := ex parameter.
            declareButton := box buttons at:2.
            declareButton pressAction:declareButton controller releaseAction.
            declareButton controller beTriggerOnDown.
            ex proceed.
        ] do:[
            action := OptionBox 
                          request:aString
                          label:(resources string:'Correctable Error')
                          image:(WarningBox iconBitmap)
                          buttonLabels:(resources array:#('Cancel' 'Declare as...' 'Correct...' 'Continue'))
                          values:#(#abort #declare #correct #continue)
                          default:#continue
                          onCancel:#abort.
        ].
    ].

    action == #declare ifTrue:[
        ^ action
    ].
    action == #abort ifTrue:[
        AbortOperationRequest raise.
        ^ false
    ].
    ^ action == #correct

    "Modified: / 05-09-2006 / 12:20:42 / cg"
!

correctableSelectorWarning:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifies us of a correctable selector warning;
     hilight the error (relPos to relEndPos) and show a Box asking for continue/correct/abort;
     this method should return true to the compiler if user wants the error
     to be corrected; false otherwise"

    |action doNotShowAgainHolder|

    self highlightingWarningPosition:relPos to:relEndPos do:[
        doNotShowAgainHolder := false asValue.
        Dialog aboutToOpenBoxNotificationSignal handle:[:ex |
            (aCompiler notNil and:[aCompiler class doNotShowCompilerWarningAgainActionQuery isHandled]) ifTrue:[
                ex parameter addCheckBox:'Do not show this dialog again (reenable via Launchers Settings Dialog)' on:doNotShowAgainHolder.
            ].
            ex proceed.
        ] do:[
            action := OptionBox 
                      request:aString
                      label:(resources string:'Warning')
                      image:(WarningBox iconBitmap)
                      buttonLabels:(resources array:#('Cancel' 'Correct...' 'Generate' 'Continue'))
                      values:#(#abort #correct #generate #continue)
                      default:#continue
                      onCancel:#abort.
        ].
        doNotShowAgainHolder value == true ifTrue:[
            aCompiler class doNotShowCompilerWarningAgainActionQuery actionQuery value
        ].
    ].

    action == #generate ifTrue:[
        ^ action
    ].

    (action isNil or:[action == #abort]) ifTrue:[
        AbortOperationRequest raise.
        ^ false
    ].
    ^ action == #correct

    "Created: / 19.1.2000 / 16:27:28 / cg"
    "Modified: / 16.11.2001 / 17:39:29 / cg"
!

error:aString position:relPos to:relEndPos asWarning:asWarning
    "obsolete - no longer invoked"

    ^ self error:aString position:relPos to:relEndPos from:nil asWarning:asWarning
!

error:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifies us of an error; hilight the error (relPos to relEndPos) 
     and show a Box asking for continue/abort."

    ^ self error:aString position:relPos to:relEndPos from:aCompiler asWarning:false
!

error:aString position:relPos to:relEndPos from:aCompiler asWarning:asWarning
    "compiler notifies us of an error; hilight the error (relPos to relEndPos)
     and show a Box asking for continue/abort."

    |answer fg bg|

    fg := asWarning ifTrue:[ self warningForegroundColor ] ifFalse:[ self errorForegroundColor ].
    bg := asWarning ifTrue:[ self warningBackgroundColor ] ifFalse:[ self errorBackgroundColor ].

    self
        highlightingErrorPosition:relPos to:relEndPos
        withForeground:fg andBackground:bg
        do:[
            |box lbl doNotShowAgainHolder l1 y1 y2 l2|

"/            Warning isHandled ifTrue:[
"/                Warning raiseErrorString:aString.
"/                ^ false
"/            ].

            "
             ask if we should abort or continue
            "
            box := YesNoBox
                    title:aString
                    yesText:(resources string:'Continue')
                    noText:(resources string:'Abort').

            lbl := aCompiler isNil ifTrue:['Compiler'] ifFalse:[aCompiler class name].
            asWarning ifTrue:[
                lbl := lbl , ' Warning'
            ] ifFalse:[
                lbl := lbl , ' Error'.
            ].
            box label:lbl.
            box image:(WarningBox iconBitmap).

            (aCompiler notNil and:[DoNotShowCompilerWarningAgainActionQuery isHandled]) ifTrue:[
                doNotShowAgainHolder := false asValue.
                box addCheckBox:'Do not show this dialog again (reenable via Launchers Settings Dialog)' on:doNotShowAgainHolder.
            ].

            answer := box confirm.

            doNotShowAgainHolder value == true ifTrue:[
                DoNotShowCompilerWarningAgainActionQuery actionQuery value
            ].

            box destroy.
        ].

    "
     do the abort if we have to
    "
    answer ifFalse:[
        AbortOperationRequest raise.
    ].
    ^ false

    "Created: / 24-11-1995 / 22:56:34 / cg"
    "Modified: / 22-09-2010 / 11:59:54 / cg"
!

highlightingErrorLine:lineNr do:aBlock
    "evaluate aBlock while some selection is shown highlighted with error colors."

    |linePosition|

    linePosition := self characterPositionOfLine:lineNr col:1.
    self highlightingErrorPosition:linePosition to:nil do:aBlock
!

highlightingErrorPosition:relPos to:relEndPos do:aBlock
    "evaluate aBlock while some selection is shown highlighted with error colors."

    self 
        highlightingErrorPosition:relPos to:relEndPos 
        withForeground:(self errorForegroundColor) andBackground:(self errorBackgroundColor) 
        do:aBlock
!

highlightingErrorPosition:relPos to:relEndPos withForeground:hilightFg andBackground:hilightBg do:aBlock
    "evaluate aBlock while some selection is shown highlighted with colors passed as args."

    |absPosition oldFg oldBg|

    "
     change color of selection & hide cursor
    "
    oldFg := selectionFgColor.
    oldBg := selectionBgColor.
    selectionBgColor := hilightBg.
    selectionFgColor := hilightFg.
    self hideCursor.

    "
     select the text - relEndPos may be nil in which case the whole line is selected
     we have to adjust the positions given by the compiler, since they
     are relative to the texts start (the compiler did stream-read the code).
    "
    codeStartPosition isNil ifTrue:[codeStartPosition := 1].
    absPosition := codeStartPosition + (relPos ? 1) - 1.
    relEndPos isNil ifTrue:[
        self selectFromCharacterPosition:absPosition.
        "/ self selectLineWhereCharacterPosition:absPosition.
    ] ifFalse:[
        self selectFromCharacterPosition:absPosition to:(codeStartPosition + (relEndPos ? 1) - 1)
    ].
    self makeSelectionVisible.

    device flush.

    aBlock ensure:[
        "
         undo selection color change and show cursor again
        "
        selectionFgColor := oldFg.
        selectionBgColor := oldBg.
        self showCursor.
    ].

    "Modified: / 16-11-2006 / 16:37:34 / cg"
!

highlightingWarningPosition:relPos to:relEndPos do:aBlock
    "evaluate aBlock while some selection is shown highlighted with warning colors."

    self 
        highlightingErrorPosition:relPos to:relEndPos 
        withForeground:(self warningForegroundColor) andBackground:(self warningBackgroundColor) 
        do:aBlock
!

unusedVariableWarning:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifies us of a (or some) unused variables;
     hilight the error (relPos to relEndPos) and show a Box asking for continue/correct/abort;
     this method should return true to the compiler if user wants the error
     to be corrected; false otherwise"

    |action doNotShowAgainHolder|

    self highlightingWarningPosition:relPos to:relEndPos do:[
        doNotShowAgainHolder := false asValue.
        Dialog aboutToOpenBoxNotificationSignal handle:[:ex |
            (aCompiler notNil and:[aCompiler class doNotShowCompilerWarningAgainActionQuery isHandled]) ifTrue:[
                ex parameter addCheckBox:'Do not show this dialog again (reenable via Launchers Settings Dialog)' on:doNotShowAgainHolder.
            ].
            ex proceed.
        ] do:[
            action := OptionBox 
                      request:aString
                      label:(resources string:'Warning')
                      image:(WarningBox iconBitmap)
                      buttonLabels:(resources array:#('Cancel' 'Remove Variable(s)' 'Continue'))
                      values:#(#abort #correct #continue)
                      default:#continue.
        ].
        doNotShowAgainHolder value == true ifTrue:[
            aCompiler class doNotShowCompilerWarningAgainActionQuery actionQuery value
        ].
    ].

    action == #abort ifTrue:[
        AbortOperationRequest raise.
        ^ false
    ].
    ^ action == #correct

    "Modified: / 07-07-2010 / 15:51:21 / cg"
!

warning:aString position:relPos to:relEndPos from:aCompiler 
    "compiler notifies us of a warning - same behavior as error"

    self error:aString position:relPos to:relEndPos from:aCompiler asWarning:true 
! !

!Workspace methodsFor:'drag & drop'!

canDrop:aDropContext
    "if allowValueDrop is true, any text- or file-object can be dropped into workspace
     (printString); otherwise, only ..."

    self isReadOnly ifTrue:[^ false].

    allowValueDrop ifTrue:[^ true].
    ^ super canDrop:aDropContext

"/    ^ aDropContext dropObjects 
"/        contains:[:someObject| (someObject isTextObject or:[ someObject isFileObject ])].

    "Created: / 16-08-2005 / 22:01:13 / janfrog"
    "Modified: / 28-11-2006 / 16:18:51 / cg"
!

doDrop:aDropContext
    self halt:'should no longer be reached'.
    self drop:aDropContext

    "Modified: / 28-11-2006 / 16:14:51 / cg"
!

drop:aDropContext
    "Any object can be dropped into workspace..."

    |textObjects nonTextObjects answer text|

    textObjects := aDropContext dropObjects 
                            select:[:dropObject | dropObject isTextObject 
                                                  or:[ dropObject isFileObject ]].
    nonTextObjects := aDropContext dropObjects 
                            reject:[:dropObject | dropObject isTextObject 
                                                  or:[ dropObject isFileObject ]].

    self dropObjects:textObjects.

    nonTextObjects notEmpty ifTrue:[
        answer := Dialog
                confirmWithCancel:(resources 
                                        string:'Drop as textual representation or as object reference ?')
                labels:(resources array:#('Cancel' 'Reference' 'Name' 'Text'))
                values:#(nil #ref #name #text)
                default:4.
        answer isNil ifTrue:[^ self].
        (answer == #text or:[answer == #name]) ifTrue:[
            text := String streamContents:[:s |
                        nonTextObjects do:[:dropObject |
                            |obj|

                            obj := dropObject theObject.
                            obj isMethod ifTrue:[
                                s nextPutAll:(answer == #name ifTrue:[obj selector] ifFalse:[obj source]).
                            ] ifFalse:[
                                obj isClass ifTrue:[
                                    s nextPutAll:(answer == #name ifTrue:[obj name] ifFalse:[obj source asString])
                                ] ifFalse:[
                                    s nextPutAll:(answer == #name ifTrue:[obj className] ifFalse:[obj printString]) .
                                ].
                            ].
                        ].
                    ].
            self paste:text.
        ] ifFalse:[
            nonTextObjects do:[:dropObject |
                name := Dialog 
                        request:(resources 
                                    string:'Name of the new Workspace Variable (refers to the dropped %1):'
                                    with:dropObject theObject class name allBold
                                 )
                        initialAnswer:'droppedObject'
                        okLabel:'Add'
                        title:'Enter Variable Name'.

                name notEmptyOrNil ifTrue:[
                    Workspace addWorkspaceVariable:name.
                    (Workspace workspaceVariableAt:name) value:dropObject theObject.
                    self paste:name.
                ].
            ].
        ]
    ].

    "Created: / 13-10-2006 / 17:34:07 / cg"
! !

!Workspace methodsFor:'editing'!

commentFrom:line1 to:line2
    "convenient function to comment out a block.
     All lines from line1 to line2 get an end-of-line comment
     in the first col 
     (if no eol comment is available, a bracketing comment is used)."

    |eolComment opening closing|

    eolComment := commentStrings at:1.
    eolComment isNil ifTrue:[
        opening := (commentStrings at:2) at:1.
        closing := (commentStrings at:2) at:2.
        (opening isNil or:[closing isNil]) ifTrue:[^ self].
    ].

    line1 to:line2 do:[:lineNr |
        |l|

        l := self listAt:lineNr.
        l isNil ifTrue:[l := ''].
        eolComment notNil ifTrue:[
            l := eolComment , l
        ] ifFalse:[
            l := opening , l , closing
        ].
        self replaceLine:lineNr with:l.
        widthOfWidestLine notNil ifTrue:[
            widthOfWidestLine := widthOfWidestLine max:(self widthOfLineString:l).
        ].
    ].
    self textChanged.

    "Created: / 09-11-1997 / 01:05:35 / cg"
    "Modified: / 09-10-2006 / 10:46:44 / cg"
!

commentSelection
    "convenient function to comment out a block.
     All lines from line1 to line2 get an end-of-line comment
     in the first col."

    |e commentPair opening closing|

    (self checkModificationsAllowed) ifFalse:[ ^ self].
    commentStrings isNil ifTrue:[ self beep. ^ self].

    selectionStartLine isNil ifTrue:[ 
        self 
            undoableDo:[ self commentFrom:cursorLine to:cursorLine ]
            info:'Comment'.
        ^ self
    ].

    self 
        undoableDo:
            [
                (selectionStartCol == 1 and:[selectionEndCol == 0]) ifTrue:[
                    self commentFrom:selectionStartLine to:selectionEndLine-1
                ] ifFalse:[
                    commentPair := commentStrings at:2 ifAbsent:nil.
                    commentPair isNil ifTrue:[
                        self beep.
                    ] ifFalse:[
                        opening := commentPair at:1.
                        closing := commentPair at:2.
                        (opening isNil or:[closing isNil]) ifTrue:[^ self].

                        e := selectionEndCol.

                        self insertString:closing atLine:selectionEndLine col:e+1.
                        self insertString:opening atLine:selectionStartLine col:selectionStartCol.

                        selectionStartLine == selectionEndLine ifTrue:[e := e + opening size].
                        self selectFromLine:selectionStartLine col:selectionStartCol
                                     toLine:selectionEndLine col:e+closing size.
                    ]
                ]
            ]
        info:'comment'

    "Created: / 9.11.1997 / 01:05:40 / cg"
    "Modified: / 5.4.1998 / 16:52:23 / cg"
!

uncommentFrom:line1 to:line2
    "convenient function to comment out a block.
     All lines from line1 to line2 get an end-of-line comment
     in the first col.
     (if no eol comment is available, a bracketing comment is removed)"

    |eolComment opening closing rest|

    eolComment := commentStrings at:1.
    eolComment isNil ifTrue:[
        opening := (commentStrings at:2) at:1.
        closing := (commentStrings at:2) at:2.
        (opening isNil or:[closing isNil]) ifTrue:[^ self].
    ] ifFalse:[
        rest := eolComment size + 1.
    ].

    line1 to:line2 do:[:lineNr |
        |l|

        l := self listAt:lineNr.
        l notNil ifTrue:[
            eolComment notNil ifTrue:[
                (l startsWith:eolComment) ifTrue:[
                    l := l copyFrom:rest
                ]
            ] ifFalse:[
                ((l startsWith:opening)
                and:[l endsWith:closing]) ifTrue:[
                    l := l copyFrom:opening size + 1.
                    l := l copyWithoutLast:closing size.
                ]
            ].
            self replaceLine:lineNr with:l.
        ]
    ].
    widthOfWidestLine := nil.
    self textChanged.

    "Created: / 09-11-1997 / 01:05:43 / cg"
    "Modified: / 09-10-2006 / 10:46:59 / cg"
!

uncommentSelection
    "convenient function to comment out a block.
     All lines from line1 to line2 get an end-of-line comment
     in the first col."

    |e commentPair opening closing sz1 sz2|

    (self checkModificationsAllowed) ifFalse:[ ^ self].
    selectionStartLine isNil ifTrue:[ 
        self 
            undoableDo:[
                self uncommentFrom:cursorLine to:cursorLine
            ]
            info:'Uncomment'.
        ^ self
    ].

    self 
        undoableDo:
            [
                (selectionStartCol == 1 and:[selectionEndCol == 0]) ifTrue:[
                    self uncommentFrom:selectionStartLine to:selectionEndLine-1
                ] ifFalse:[
                    commentPair := commentStrings at:2.
                    opening := commentPair at:1.
                    closing := commentPair at:2.
                    (opening isNil or:[closing isNil]) ifTrue:[^ self].

                    sz1 := opening size.
                    sz2 := closing size.

                    ((self 
                        stringAtLine:selectionStartLine 
                        from:selectionStartCol
                        to:selectionStartCol+sz1 - 1) = opening
                    and:[(self 
                        stringAtLine:selectionEndLine 
                        from:selectionEndCol - sz2 + 1
                        to:selectionEndCol) = closing ]) ifTrue:[

                        self deleteCharsAtLine:selectionEndLine fromCol:selectionEndCol-sz2+1 toCol:selectionEndCol.
                        self deleteCharsAtLine:selectionStartLine fromCol:selectionStartCol toCol:selectionStartCol+sz1-1.

                        e := selectionEndCol - sz2.
                        selectionStartLine == selectionEndLine ifTrue:[e := e - sz1].
                        self selectFromLine:selectionStartLine col:selectionStartCol
                                     toLine:selectionEndLine col:e.
                    ]
                ]
            ]
        info:'uncomment'

    "Modified: / 7.1.1997 / 20:13:32 / cg"
    "Created: / 9.11.1997 / 01:05:46 / cg"
! !

!Workspace methodsFor:'event handling'!

keyPress:key x:x y:y
    <resource: #keyboard (#DoIt #InspectIt #PrintIt #ReplaceIt #BrowseIt #ExpandIt)>

    (key == #DoIt)      ifTrue:[self doIt. ^ self].
    (key == #InspectIt) ifTrue:[self inspectIt. ^ self].
    (key == #PrintIt)   ifTrue:[self printIt. ^ self].
    (key == #ReplaceIt) ifTrue:[self replaceIt. ^ self].
    (key == #BrowseIt)  ifTrue:[self browseIt. ^ self].
    (key == #ImplementorsOfIt)   ifTrue:[self browseImplementorsOfIt. ^ self].
    (key == #ExpandAbbreviation) ifTrue:[self expandAbbreviation. ^ self].

    super keyPress:key x:x y:y

    "Modified: / 08-11-2007 / 11:29:45 / cg"
! !

!Workspace methodsFor:'executing'!

do:code withValueDo:aBlock
    "helper for doIt, printIt and inspectIt. 
     Evaluate the selection and, if all went well, evaluate the argument, 
     aBlock with the value.
     Most work is in preparing for proper cleanup in case of abort
     or other exception while the evaluation is performed.
     (restore cursor, selectionColors etc.)"

    |selLine selCol endLine endCol cLine cCol cleanUp executeBlock|

    code notNil ifTrue:[
        code asString withoutSeparators isEmpty ifTrue:[ ^ self ].

        codeStartPosition := self characterPositionOfSelection.

        "
         remember selection for later - if there is an error,
         the notification method will highlight it.
         thus destroying the current selection
        "
        selLine := selectionStartLine.
        selCol := selectionStartCol.
        endLine := selectionEndLine.
        endCol := selectionEndCol.
        cCol := cursorCol.
        cLine := cursorLine.

        "
         cleanup: restore previous selection and cursor positions
        "
        cleanUp := [
                self selectFromLine:selLine col:selCol toLine:endLine col:endCol.
                cLine notNil ifTrue:[
                    self cursorLine:cLine col:cCol
                ].
        ].

        "
         perform the action.
         Be careful to release the reference to the value;
         otherwise, we could keep lots of garbage from being freed
         until the view gets closed
        "
        executeBlock := [
                [
                    AbortOperationRequest handle:[:ex |
                         "/ aBlock value:'** Abortsignal cought **'.
                         ex return
                    ] do:[
                        |value|

                        doItAction notNil ifTrue:[
                            value := doItAction value:(code asString).
                            cleanUp value. cleanUp := nil.
                            aBlock notNil ifTrue:[
                                aBlock value:value.
                            ].
                            value := nil.
                            self class rememberDoIt:code.
                        ]
                    ]
                ] ensure:[
                    cleanUp notNil ifTrue:[
                        cleanUp value. cleanUp := nil
                    ].
                ]
            ].
        aBlock isNil ifTrue:[
            "no action is performed with the result - give the user a visible
             feedback, that something has been done"
            self topView withVisibleCursor:Cursor execute do:executeBlock.
        ] ifFalse:[
            self topView withCursor:Cursor execute do:executeBlock.
        ].
    ]

    "Modified: / 22.4.1998 / 21:56:13 / ca"
    "Created: / 22.4.1998 / 21:57:05 / ca"
    "Modified: / 26.9.2001 / 17:32:59 / cg"
! !

!Workspace methodsFor:'initialization & release'!

doItAction:aOneArgBlock
    "define the action to be performed when 'doIt' is selected.
     The block will be evaluated, passing the selection as a String argument. 
     A default doItAction is set for you in the initialize method."

    doItAction := aOneArgBlock

    "Modified: 27.2.1996 / 15:31:37 / cg"
!

executeDoIt:theCode
    ^ (self compilerClass new) moreSharedPools:poolsConsideredInDoIts;
        evaluate:theCode 
        in:nil 
        receiver:simulatedSelf 
        notifying:self 
        logged:true 
        ifFail:nil 
!

initStyle
    "setup viewStyle specifics"

    super initStyle.

    DefaultViewBackground notNil ifTrue:[
        viewBackground := DefaultViewBackground.
        self backgroundColor:viewBackground.
    ].
!

initialize
    super initialize.

    scrollWhenUpdating := #beginOfText.
    showMatchingParenthesis := true.
    allowValueDrop := true.

    commentStrings := #(
                        '"/'
                        ('"' '"')
                       ).

    self initializeDoITAction.
    self initializeDragAndDrop.

    "Modified: / 28-11-2006 / 16:11:55 / cg"
!

initializeDoITAction
    "set up the block to be evaluated for doIts.
     This is done here in a separate method to allow easier
     redefinition in subclasses"

    doItAction := [:theCode | self executeDoIt:theCode].
!

initializeDragAndDrop
    |target|

    target := DropTarget 
                    receiver:self
                    argument:nil
                    dropSelector:#drop:
                    canDropSelector:#canDrop:.
    self dropTarget:target

    "Created: / 16-08-2005 / 22:03:36 / janfrog"
    "Modified: / 13-10-2006 / 12:37:18 / cg"
! !

!Workspace methodsFor:'menu & menu actions'!

browseClass
    "user selected 'browseClass' from menu; evaluate the code
     and open a browser on the resulting class (if it evaluates to one)"

    ^ self 
        do:(self selectionAsString) 
        withValueDo:[:result | 
            result isBehavior ifTrue:[
                result browserClass openInClass:result selector:nil
            ] ifFalse:[
                self warn:'Selection does not evaluate to a class'
            ]
        ].

    "Modified: / 26.9.2001 / 17:37:35 / cg"
!

browseImplementorsOfIt
    "open a browser on the implementors of the selected text"

    |selectedText selector browserClass na|

    selectedText := self selectionAsString.
    selectedText size > 0 ifTrue:[
        self windowGroup withWaitCursorDo:[
            "/ hack, for now and expecco; must ask the Parser eventually...
            (compilerClass notNil and:[compilerClass includesBehavior:JavaScriptParser]) ifTrue:[
                "/ selector is in one piece anyway
                (selectedText includes:$_) ifFalse:[
                    "/ zero or one args - sigh (need to parse more to figure this out)
                    selector := JavaScriptParser basicNew translatedSmalltalkSelectorFor:selectedText numArgs:1.
                    selectedText := JavaScriptParser basicNew translatedSmalltalkSelectorFor:selectedText numArgs:0.
                ] ifTrue:[
                    "/ count _#s plus one arg - sigh
                    na := (selectedText occurrencesOf:$_) + 1. 
                    selector := JavaScriptParser basicNew translatedSmalltalkSelectorFor:selectedText numArgs:na
                ].
            ] ifFalse:[
                selector := SystemBrowser extractSelectorFrom:selectedText.
            ].
            browserClass := UserPreferences current systemBrowserClass.

            (selector notNil and:[selector ~= selectedText]) ifTrue:[
                (SystemBrowser 
                        findImplementorsOfAny:(Array with:selectedText) 
                        in:(Smalltalk allClasses) 
                        ignoreCase:false) isEmpty ifTrue:[
                    browserClass browseImplementorsOf:selector
                ] ifFalse:[
                    browserClass browseImplementorsOfAny:(Set with:selector with:selectedText)
                ].
            ] ifFalse:[
                browserClass browseImplementorsOf:(selector ? selectedText)
            ]
        ]
    ].

    "Created: / 5.11.2001 / 17:32:23 / cg"
    "Modified: / 19.11.2001 / 22:15:17 / cg"
!

browseIt
    "evaluate the code and open a browser on the resulting class (if it evaluates to one),
     or the class of the resulting object (if it does not evaluate to a class).

     Added feature: if selection is of the form class >> selector,  immediately switch to that selector."

    |codeToEvaluate idx selector evaluatedValue classToBrowse|

    codeToEvaluate := (self selectionOrTextOfCursorLine ? '') withoutSeparators.
    idx := codeToEvaluate indexOfSubCollection:'>>'.
    idx ~~ 0 ifTrue:[
        selector := (codeToEvaluate copyFrom:idx+2) withoutSeparators string.
        (selector startsWith:'#') ifTrue:[
            selector := Symbol readFrom:selector.
        ].
        codeToEvaluate := codeToEvaluate copyTo:idx-1.   
    ].

    Parser parseErrorSignal handle:[:ex |
        |className|

        "/ fallback, if garbage is selected, look for matching classes.
        className := SystemBrowser
            askForClassNameMatching:codeToEvaluate 
            inEnvironment:nil 
            for:nil.
        className isNil ifTrue:[^ self].
        classToBrowse := Smalltalk classNamed:className.
    ] do:[
        self 
            do:codeToEvaluate 
            withValueDo:[:result | evaluatedValue := result].

        evaluatedValue isNil ifTrue:[
            codeToEvaluate asCollectionOfWords size == 1 ifTrue:[
                codeToEvaluate isUppercaseFirst ifTrue:[
                    Dialog information:(codeToEvaluate allBold , ' is unbound or nil').
                    ^ self.
                ].
            ]
        ].
        classToBrowse := evaluatedValue isBehavior 
                        ifTrue:[ evaluatedValue ] 
                        ifFalse:[ evaluatedValue class ].
    ].

    classToBrowse browserClass openInClass:classToBrowse selector:selector
!

browseItsClass
    "user selected 'browseItsClass' from menu; evaluate the code
     and open a browser on the results class"

    ^ self 
        do:(self selectionAsString) 
        withValueDo:[:result | 
                        result class browserClass openInClass:result class selector:nil
                    ]

    "Modified: / 26.9.2001 / 17:38:06 / cg"
!

browseReferencesToIt
    "open a browser on all references to the selected global"

    |sel|

    sel := self selectionAsString.
    sel size > 0 ifTrue:[
        self windowGroup withWaitCursorDo:[
            (Smalltalk includesKey:sel asSymbol) ifFalse:[
                sel := ((NameSpace allNameSpaces detect:[:ns | ns includesKey:sel asSymbol]) at:sel asSymbol) name.
             ].
            (UserPreferences current systemBrowserClass)
                browseReferendsOf:sel
        ].
    ].

    "Created: / 5.11.2001 / 17:32:23 / cg"
    "Modified: / 5.11.2001 / 17:32:38 / cg"
!

browseSendersOfIt
    "open a browser on the senders of the selected text"

    |selectedText selector|

    selectedText := self selectionAsString.
    selectedText size > 0 ifTrue:[
        self windowGroup withWaitCursorDo:[
            selector := SystemBrowser extractSelectorFrom:selectedText.
            (UserPreferences current systemBrowserClass)
                browseAllCallsOn:(selector ? selectedText)
        ]
    ].

    "Created: / 5.11.2001 / 17:32:23 / cg"
    "Modified: / 19.11.2001 / 22:15:27 / cg"
!

doIt
    "user selected 'doIt' from menu; show a wait-cursor, evaluate the code
     and finally restore cursor; return result of evaluation"

    ^ self 
        do:(self selectionOrTextOfCursorLine) 
        withValueDo:nil

    "Modified: / 16.5.1998 / 16:45:01 / cg"
!

editMenu
    "return my popUpMenu; thats the superclasses menu
     PLUS st-evaluation items: doIt, printIt and inspectIt."

    <resource: #keyboard (#DoIt #PrintIt #InspectIt 
                          #CommentSelection #UncommentSelection
                          #BrowseIt #ImplementorsOfIt
                         )>
    <resource: #programMenu>

    |m sub subsub idx sensor s s2|

    m := super editMenu.
    ((sensor := self sensor) notNil and:[sensor ctrlDown and:[sensor shiftDown not]]) ifTrue:[
        sub := m.
        m := nil.
    ] ifFalse:[
        sub := m subMenuAt:#others.
    ].

    sub notNil ifTrue:[

        "
         workspaces support #browse, implementors etc. add them after paste.
        "
        sub 
            addItemList:#(
                ('-'                                                               )
                ('Browse'                       browseIt                BrowseIt            )
                ('Senders of It'                browseSendersOfIt                           )
                ('Implementors of It'           browseImplementorsOfIt  ImplementorsOfIt    )
                ('References to It'             browseReferencesToIt                        )
"/                ('Classes Containing It in Name'    browseClassesContainingItInName           )
"/                ('Methods Contaníning It in Name'   browseMethodsContainingItInName           )
"/                ('Methods Contaníning It in Source' browseMethodsContainingItInSource         )
                ('-'                                                                )
                ('TimeIt'               timeIt                                      )
                ('SpyOnIt'              spyOnIt                                     ))
          resources:resources  
          after:#gotoLine.

        subsub := sub subMenuAt:#tools.
        subsub notNil ifTrue:[
            subsub
                addItemList:#(
                    ('-'                                                                )
                    ('Comment'              commentSelection        CommentSelection    )
                    ('Uncomment'            uncommentSelection      UncommentSelection  ))
              resources:resources  
              after:#'indent'.
        ].

        self hasSelection ifFalse:[
            sub disableAll:#(browseImplementorsOfIt browseSendersOfIt browseReferencesToIt timeIt spyOnIt
                             commentSelection uncommentSelection ) 
        ] ifTrue:[
            s := self selectionAsString.
            s notNil ifTrue:[
                s asSymbolIfInterned isNil ifTrue:[
                    s2 := SystemBrowser extractSelectorFrom:s.
                    s2 notNil ifTrue:[
                        s2 := s2 asSymbolIfInterned.
                    ].
                ].
            ].
            (s2 isNil and:[s isNil]) ifTrue:[
                sub disableAll:#(browseImplementorsOfIt browseSendersOfIt).
            ].
            "/ a global or namespace-var selected ?
            (s notNil 
            and:[s asSymbolIfInterned notNil
            and:[ (Smalltalk includesKey:s asSymbol)
                  or:[ (NameSpace allNameSpaces contains:[:ns | ns includesKey:s asSymbol]) ]]])
            ifFalse:[
                sub disable:#browseReferencesToIt.
            ].
            self isReadOnly ifTrue:[
                sub disableAll:#(commentSelection uncommentSelection) 
            ].
        ].
    ].

    m notNil ifTrue:[
        "
         workspaces support #doIt, #printIt and #inspectIt
         add them after paste.
        "
        idx := m indexOf:#paste.
        idx == 0 ifTrue:[idx := m indexOf:#pasteOrReplace].
        idx ~~ 0 ifTrue:[
            m 
              addItemList:#(
                ('-'                                )
                ('DoIt'         doIt        DoIt     )
                ('PrintIt'      printIt     PrintIt  )
                ('InspectIt'    inspectIt   InspectIt))
              resources:resources  
              after:idx.
            
        ].

        (self hasSelectionOrTextInCursorLine) ifFalse:[
            |lNr line|

            lNr := self cursorLine.
            line := self listAt:lNr.
            line isEmptyOrNil ifTrue:[
                m disableAll:#(printIt doIt inspectIt browseIt) 
            ].
        ].
        self isReadOnly ifTrue:[
            m disable:#printIt 
        ].
    ].

    ^ m ? sub.

    "Modified: / 22.4.1998 / 21:49:06 / ca"
    "Modified: / 19.11.2001 / 23:12:01 / cg"
!

inspectIt
    "user selected 'inspectIt' from menu; use doIt to evaluate the code
     and start an inspector on the result"

    |shifted|

    shifted := self sensor shiftDown.

    ^ self 
        do:(self selectionOrTextOfCursorLine) 
        withValueDo:[:result | shifted ifTrue:[result basicInspect] ifFalse:[result inspect] ]

    "Modified: / 16.5.1998 / 16:44:56 / cg"
!

printIt
    "user selected 'printIt' from menu; use doIt to evaluate the code
     and insert result of evaluation into my text.
     If the text is readOnly, do nothing."

    self isReadOnly ifTrue:[
        self beep.
        ^ self.
    ].
    self 
        undoableDo:[
            self 
                do:(self selectionOrTextOfCursorLine) 
                withValueDo:[:result | 
                    self cursorLine:selectionEndLine col:(selectionEndCol + 1).
                    self insertSelectedStringAtCursor:(result displayString "printString")
                ]
        ] 
        info:'PrintIt'

    "Modified: / 08-11-2007 / 11:35:31 / cg"
!

replaceIt
    "like printIt, but replace the selection with the result, instead of
     pasting it after the selection."

    self isReadOnly ifTrue:[
        self beep.
        ^ self
    ].

    self 
        undoableDo:[
            self 
                do:(self selectionOrTextOfCursorLine) 
                withValueDo:[:result |
                    self replaceSelectionBy:(result displayString "printString")
                ].
                undoSupport actionInfo:'ReplaceIt'.
        ]
        info:'ReplaceIt'

    "Created: / 08-11-2007 / 11:31:54 / cg"
!

spyOnIt
    "user selected 'spyOnIt' from menu; show a wait-cursor, evaluate the code
     and finally restore cursor; return result of evaluation"

    |code|

    code := 'MessageTally spyDetailOn:[' , self selectionAsString, ']'.
    self do:code withValueDo:[:value| ].

    "Modified: / 22-04-1998 / 22:03:53 / ca"
    "Modified: / 08-11-2007 / 11:36:38 / cg"
!

timeIt
    "user selected 'timeIt' from menu; show a wait-cursor, evaluate the code
     and finally restore cursor; return result of evaluation"

    |code|

    code := '|t| t := Time millisecondsToRun:[' , self selectionAsString, '].
             Transcript showCR:''execution time: '' , t printString , '' ms''.'.
    self do:code withValueDo:[:value | ].

    "Modified: / 22.4.1998 / 22:03:51 / ca"
! !

!Workspace methodsFor:'misc'!

expandAbbreviation
    "after receiving an Alt-shift key-event, look for the string before the
     cursor, find an abbrev for it and expand."

    |expandedString abortExpandAction oldSelectionStartLine oldSelectionStartCol oldSelectionEndLine oldSelectionEndCol oldCursorLine oldCursorCol
     newCursorPos replStartCol|

    oldCursorLine := self cursorLine.
    oldCursorCol := self cursorCol.
    oldSelectionStartLine := self selectionStartLine.
    oldSelectionStartCol := self selectionStartCol.
    oldSelectionEndLine := self selectionEndLine.
    oldSelectionEndCol := self selectionEndCol.

    abortExpandAction := [
        self selectFromLine:oldSelectionStartLine 
                              col:oldSelectionStartCol 
                           toLine:oldSelectionEndLine 
                              col:oldSelectionEndCol.
        self cursorLine: oldCursorLine col: oldCursorCol.
    ].                                  

    expandedString := self findAbbreviationKeyBeforeCursor.
    expandedString isNil ifTrue:[
        abortExpandAction value.
        ^ self
    ].
    newCursorPos := expandedString indexOf:$!!.
    newCursorPos ~~ 0 ifTrue:[
        expandedString := expandedString copyWithout:$!!.
    ].
    replStartCol := self selectionStartCol.
    self
        undoableDo:[
            self replaceSelectionWith: expandedString]
        info:'Replace'.

    newCursorPos == 0 ifTrue:[
        "/ cursor already fine (at the end)
    ] ifFalse:[
        self cursorCol:replStartCol+newCursorPos-1
    ]
!

findAbbreviationKeyBeforeCursor
    "after receiving an Alt-shift key-event, look for the string before the
     cursor, find an abbrev for it and expand."

    |sniplets keys minMax maxKeyLen minKeyLen line stringBeforeCursor|

    sniplets := self class sniplets.
    keys := sniplets keys.
    minMax := (keys collect:[:k | k size]) minMax.
    minKeyLen := minMax first.
    maxKeyLen := minMax second.
    line := (self at:cursorLine) ? ''.
    stringBeforeCursor := line string copyTo:((cursorCol-1) min:line size).

    maxKeyLen := maxKeyLen min:stringBeforeCursor size.

    (maxKeyLen to:minKeyLen by:-1) do:[:l |
        |lCharactersBeforeCursor expandedString|

        lCharactersBeforeCursor := stringBeforeCursor last:l.
        expandedString := sniplets at:lCharactersBeforeCursor ifAbsent:nil.
        expandedString notNil ifTrue:[
            self selectFromLine:cursorLine col:cursorCol-l toLine:cursorLine col:cursorCol-1.
            ^ expandedString
        ].
    ].
    ^ nil.
! !

!Workspace methodsFor:'queries'!

isWorkspace
    ^ true
! !

!Workspace class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg/Workspace.st,v 1.220 2010-09-22 12:22:56 cg Exp $'
!

version_CVS
    ^ '$Header: /cvs/stx/stx/libwidg/Workspace.st,v 1.220 2010-09-22 12:22:56 cg Exp $'
! !