123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 |
- Smalltalk createPackage: 'Helios-Workspace'!
- (Smalltalk packageAt: 'Helios-Workspace' ifAbsent: [ self error: 'Package not created: Helios-Workspace' ]) imports: {'codeMirrorLib' -> 'codemirror/lib/codemirror'. 'codemirror/addon/hint/show-hint'. 'codemirror/mode/smalltalk/smalltalk'}!
- Object subclass: #HLCodeModel
- slots: {#announcer. #environment. #receiver}
- package: 'Helios-Workspace'!
- !HLCodeModel methodsFor: 'accessing'!
- announcer
- ^ announcer ifNil: [ announcer := Announcer new ]
- !
- environment
- ^ environment ifNil: [ HLManager current environment ]
- !
- environment: anEnvironment
- environment := anEnvironment
- !
- receiver
- ^ receiver ifNil: [ receiver := self defaultReceiver ]
- !
- receiver: anObject
- receiver := anObject
- ! !
- !HLCodeModel methodsFor: 'actions'!
- browse: anObject
- anObject browse
- !
- doIt: aString
- "Evaluate aString in the receiver's `environment`.
-
- Note: Catch any error and handle it manually, bypassing
- boot.js behavior to avoid the browser default action on
- ctrl+d/ctrl+p.
-
- See https://lolg.it/amber/amber/issues/882"
- ^ [ self environment evaluate: aString for: self receiver ]
- tryCatch: [ :e |
- ErrorHandler handleError: e.
- nil ]
- !
- inspect: anObject
- self environment inspect: anObject
- ! !
- !HLCodeModel methodsFor: 'defaults'!
- defaultReceiver
- ^ self environment doItReceiver
- ! !
- !HLCodeModel class methodsFor: 'actions'!
- on: anEnvironment
- ^ self new
- environment: anEnvironment;
- yourself
- ! !
- HLWidget subclass: #HLCodeWidget
- slots: {#model. #wrapper. #code. #editor. #state}
- package: 'Helios-Workspace'!
- !HLCodeWidget methodsFor: 'accessing'!
- announcer
- ^ self model announcer
- !
- contents
- ^ editor getValue
- !
- contents: aString
- editor setValue: aString.
- state ifNotNil: [ self updateState ]
- !
- currentLine
- ^editor getLine: (editor getCursor line)
- !
- currentLineOrSelection
- ^editor somethingSelected
- ifFalse: [ self currentLine ]
- ifTrue: [ self selection ]
- !
- editorOptions
- ^ #{
- 'theme' -> ('helios.editorTheme' settingValueIfAbsent: 'default').
- 'mode' -> 'text/x-stsrc'.
- 'inputStyle' -> 'contenteditable'.
- 'lineNumbers' -> true.
- 'enterMode' -> 'flat'.
- 'indentWithTabs' -> true.
- 'indentUnit' -> 4.
- 'matchBrackets' -> true.
- 'electricChars' -> false.
- 'keyMap' -> 'Amber'.
- 'extraKeys' -> (HashedCollection with: ('helios.completionKey' settingValueIfAbsent: 'Shift-Space') -> 'autocomplete')
- }
- !
- model
- ^ model ifNil: [ model := HLCodeModel new ]
- !
- model: aModel
- model := aModel
- !
- receiver
- ^ self model receiver
- !
- receiver: anObject
- self model receiver: anObject
- !
- selection
- ^editor getSelection
- !
- selectionEnd
- ^ code asDomNode selectionEnd
- !
- selectionEnd: anInteger
- code asDomNode selectionEnd: anInteger
- !
- selectionStart
- ^ code asDomNode selectionStart
- !
- selectionStart: anInteger
- code asDomNode selectionStart: anInteger
- ! !
- !HLCodeWidget methodsFor: 'actions'!
- browseIt
- | result |
-
- result := [ self doIt ] on: Error do: [ :exception |
- ^ ErrorHandler handleError: exception ].
-
- self model browse: result
- !
- clear
- self contents: ''
- !
- configureEditor
- self editor at: 'amberCodeWidget' put: self.
- self editor on: 'change' do: [ self onChange ].
- self wrapper asJQuery on: 'mousedown' in: '.CodeMirror pre' do: [ :event | | position node |
- (event at: 'ctrlKey') ifTrue: [
- position := self editor coordsChar: #{
- 'left' -> event clientX.
- 'top' -> event clientY
- }.
- self onCtrlClickAt: (position line @ position ch) + 1.
- event preventDefault ] ]
- !
- doIt
- | result |
- result := self model doIt: self currentLineOrSelection.
- self model announcer announce: (HLDoItExecuted on: model).
- ^ result
- !
- editor
- ^ editor
- !
- focus
- editor focus
- !
- inspectIt
- self model inspect: self doIt
- !
- navigateTo: aString
- Finder findString: aString
- !
- navigateToReference: aString
- (HLReferences openAsTab)
- search: aString
- !
- print: aString
- | start stop currentLine |
- currentLine := (editor getCursor: false) line.
- start := HashedCollection new.
- start at: 'line' put: currentLine.
- start at: 'ch' put: (editor getCursor: false) ch.
- (editor getSelection) ifEmpty: [
- "select current line if selection is empty"
- start at: 'ch' put: (editor getLine: currentLine) size.
- editor setSelection: #{'line' -> currentLine. 'ch' -> 0} end: start.
- ].
- stop := HashedCollection new.
- stop at: 'line' put: currentLine.
- stop at: 'ch' put: ((start at: 'ch') + aString size + 2).
- editor replaceSelection: (editor getSelection, ' ', aString, ' ').
- editor setCursor: (editor getCursor: true).
- editor setSelection: stop end: start
- !
- printIt
- self print: self doIt printString
- !
- saveIt
- "I do not do anything"
- !
- setEditorOn: aTextarea
- editor := codeMirrorLib provided fromTextArea: aTextarea options: self editorOptions
- ! !
- !HLCodeWidget methodsFor: 'hints'!
- messageHintFor: anEditor token: aToken
- ^ (Smalltalk core allSelectors asArray
- select: [ :each | each includesSubString: aToken string ])
- reject: [ :each | each = aToken string ]
- !
- variableHintFor: anEditor token: aToken
- | variables classNames pseudoVariables |
-
- variables := (anEditor display wrapper asJQuery find: 'span.cm-variable') get
- collect: [ :each | each asJQuery html ].
-
- classNames := Smalltalk classes collect: [ :each | each name ].
- pseudoVariables := Smalltalk pseudoVariableNames.
-
- ^ ((variables, classNames, pseudoVariables) asSet asArray sort
- select: [ :each | each includesSubString: aToken string ])
- reject: [ :each | each = aToken string ]
- ! !
- !HLCodeWidget methodsFor: 'reactions'!
- onChange
- self updateState
- !
- onCtrlClickAt: aPoint
- | ast node |
-
- ast := [ Smalltalk parse: self editor getValue ]
- on: Error
- do: [ :error | ^ self ].
-
- node := ast
- navigationNodeAt: aPoint
- ifAbsent: [ ^ nil ].
-
- self navigateTo: node navigationLink
- !
- onInspectIt
- self inspectIt
- !
- onPrintIt
- self printIt
- !
- onSaveIt
- "I do not do anything"
- ! !
- !HLCodeWidget methodsFor: 'rendering'!
- renderButtonsOn: html
- html button
- class: 'button';
- with: 'DoIt';
- onClick: [ self doIt ].
- html button
- class: 'button';
- with: 'PrintIt';
- onClick: [ self printIt ].
- html button
- class: 'button';
- with: 'InspectIt';
- onClick: [ self inspectIt ].
- html button
- class: 'button';
- with: 'BrowseIt';
- onClick: [ self browseIt ]
- !
- renderContentOn: html
- html div class: 'editor'; with: [
- code := html textarea ].
- state := html div class: 'state'.
-
- html div
- class: 'buttons_bar';
- with: [ self renderButtonsOn: html ].
-
- self
- setEditorOn: code asDomNode;
- configureEditor;
- updateState
- ! !
- !HLCodeWidget methodsFor: 'testing'!
- canHaveFocus
- ^ true
- !
- hasFocus
- ^ code asJQuery is: ':active'
- !
- hasModification
- ^ false
- ! !
- !HLCodeWidget methodsFor: 'updating'!
- updateState
- self hasModification
- ifTrue: [ state asJQuery addClass: 'modified' ]
- ifFalse: [ state asJQuery removeClass: 'modified' ]
- ! !
- !HLCodeWidget class methodsFor: 'accessing'!
- keyMap
- ^ HLManager current keyBinder systemIsMac
- ifTrue: [ self macKeyMap ]
- ifFalse: [ self pcKeyMap ]
- !
- macKeyMap
- ^ #{
- 'Alt-Backspace' -> 'delWordBefore'.
- 'Alt-Delete' -> 'delWordAfter'.
- 'Alt-Left' -> 'goWordLeft'.
- 'Alt-Right' -> 'goWordRight'.
- 'Cmd-A' -> 'selectAll'.
- 'Cmd-Alt-F' -> 'replace'.
- 'Cmd-D' -> 'doIt'.
- 'Cmd-B' -> 'browseIt'.
- 'Cmd-Down' -> 'goDocEnd'.
- 'Cmd-End' -> 'goDocEnd'.
- 'Cmd-F' -> 'find'.
- 'Cmd-G' -> 'findNext'.
- 'Cmd-I' -> 'inspectIt'.
- 'Cmd-Left' -> 'goLineStart'.
- 'Cmd-P' -> 'printIt'.
- 'Cmd-Right' -> 'goLineEnd'.
- 'Cmd-S' -> 'saveIt'.
- 'Cmd-Up' -> 'goDocStart'.
- 'Cmd-Y' -> 'redo'.
- 'Cmd-Z' -> 'undo'.
- 'Cmd-[' -> 'indentLess'.
- 'Cmd-]' -> 'indentMore'.
- 'Ctrl-Alt-Backspace' -> 'delWordAfter'.
- 'Shift-Cmd-Alt-F' -> 'replaceAll'.
- 'Shift-Cmd-G' -> 'findPrev'.
- 'Shift-Cmd-Z' -> 'redo'.
- 'fallthrough' -> { 'basic'. 'emacsy' }
- }
- !
- pcKeyMap
- ^ #{
- 'Alt-Left' -> 'goLineStart'.
- 'Alt-Right' -> 'goLineEnd'.
- 'Alt-Up' -> 'goDocStart'.
- 'Ctrl-A' -> 'selectAll'.
- 'Ctrl-Backspace' -> 'delWordBefore'.
- 'Ctrl-D' -> 'doIt'.
- 'Ctrl-B' -> 'browseIt'.
- 'Ctrl-Delete' -> 'delWordAfter'.
- 'Ctrl-Down' -> 'goDocEnd'.
- 'Ctrl-End' -> 'goDocEnd'.
- 'Ctrl-F' -> 'find'.
- 'Ctrl-G' -> 'findNext'.
- 'Ctrl-I' -> 'inspectIt'.
- 'Ctrl-Home' -> 'goDocStart'.
- 'Ctrl-Left' -> 'goWordLeft'.
- 'Ctrl-P' -> 'printIt'.
- 'Ctrl-Right' -> 'goWordRight'.
- 'Ctrl-S' -> 'saveIt'.
- 'Ctrl-Y' -> 'redo'.
- 'Ctrl-Z' -> 'undo'.
- 'Ctrl-[' -> 'indentLess'.
- 'Ctrl-]' -> 'indentMore'.
- 'Shift-Ctrl-F' -> 'replace'.
- 'Shift-Ctrl-G' -> 'findPrev'.
- 'Shift-Ctrl-R' -> 'replaceAll'.
- 'Shift-Ctrl-Z' -> 'redo'.
- 'fallthrough' -> #('basic')
- }
- ! !
- !HLCodeWidget class methodsFor: 'hints'!
- hintFor: anEditor options: options
- | cursor token completions |
-
- cursor := anEditor getCursor.
- token := anEditor getTokenAt: cursor.
- token at: 'state' put: ((codeMirrorLib basicAt: 'innerMode')
- value: anEditor getMode value: (token at: 'state')) state.
-
- completions := token type = 'variable'
- ifTrue: [ HLCodeWidget variableHintFor: anEditor token: token ]
- ifFalse: [ HLCodeWidget messageHintFor: anEditor token: token ].
-
- ^ #{
- 'list' -> completions.
- 'from' -> ((codeMirrorLib basicAt: 'Pos') value: cursor line value: token end).
- 'to' -> ((codeMirrorLib basicAt: 'Pos') value: cursor line value: token start)
- }
- !
- messageHintFor: anEditor token: aToken
- ^ (anEditor at: 'amberCodeWidget')
- messageHintFor: anEditor token: aToken
- !
- variableHintFor: anEditor token: aToken
- ^ (anEditor at: 'amberCodeWidget')
- variableHintFor: anEditor token: aToken
- ! !
- !HLCodeWidget class methodsFor: 'initialization'!
- initialize
- super initialize.
- self
- setupCodeMirror;
- setupCommands;
- setupKeyMaps.
- !
- setupCodeMirror
- <inlineJS: '
- codeMirrorLib.keyMap["default"].fallthrough = ["basic"];
- codeMirrorLib.commands.autocomplete = function(cm) {
- codeMirrorLib.showHint(cm, $self._hintFor_options_.bind($self));
- }
- '>
- !
- setupCommands
- (codeMirrorLib basicAt: 'commands')
- at: 'doIt' put: [ :cm | cm amberCodeWidget doIt ];
- at: 'inspectIt' put: [ :cm | cm amberCodeWidget inspectIt ];
- at: 'printIt' put: [ :cm | cm amberCodeWidget printIt ];
- at: 'saveIt' put: [ :cm | cm amberCodeWidget saveIt ];
- at: 'browseIt' put: [ :cm | cm amberCodeWidget browseIt ]
- !
- setupKeyMaps
- <inlineJS: 'codeMirrorLib.keyMap["Amber"] = $self._keyMap()'>
- ! !
- HLCodeWidget subclass: #HLNavigationCodeWidget
- slots: {#methodContents}
- package: 'Helios-Workspace'!
- !HLNavigationCodeWidget methodsFor: 'accessing'!
- configureEditor
- super configureEditor.
- self contents: self methodContents
- !
- contents: aString
- self methodContents: aString.
- super contents: aString
- !
- methodContents
- ^ methodContents ifNil: [ '' ]
- !
- methodContents: aString
- ^ methodContents := aString
- !
- previous
- "for browser lists widget"
- !
- previous: aWidget
- "for browser lists widget"
- ! !
- !HLNavigationCodeWidget methodsFor: 'testing'!
- hasModification
- ^ (self methodContents = self contents) not
- ! !
- !HLNavigationCodeWidget class methodsFor: 'instance creation'!
- on: aBrowserModel
- ^ self new
- browserModel: aBrowserModel;
- yourself
- ! !
- !HLNavigationCodeWidget class methodsFor: 'testing'!
- canBeOpenAsTab
- ^ false
- ! !
- HLNavigationCodeWidget subclass: #HLBrowserCodeWidget
- slots: {#browserModel}
- package: 'Helios-Workspace'!
- !HLBrowserCodeWidget methodsFor: 'accessing'!
- browserModel
- ^ browserModel
- !
- browserModel: aBrowserModel
- browserModel := aBrowserModel.
- self
- observeSystem;
- observeBrowserModel
- ! !
- !HLBrowserCodeWidget methodsFor: 'actions'!
- observeBrowserModel
- self browserModel announcer
- on: HLSaveSourceCode
- send: #onSaveIt
- to: self;
-
- on: HLShowInstanceToggled
- send: #onShowInstanceToggled
- to: self;
-
- on: HLSourceCodeSaved
- send: #onSourceCodeSaved
- to: self;
-
- on: HLAboutToChange
- send: #onBrowserAboutToChange:
- to: self;
-
- on: HLParseErrorRaised
- send: #onParseError:
- to: self;
-
- on: HLCompileErrorRaised
- send: #onCompileError:
- to: self;
-
- on: HLUnknownVariableErrorRaised
- send: #onUnknownVariableError:
- to: self;
-
- on: HLInstVarAdded
- send: #onInstVarAdded
- to: self;
-
- on: HLMethodSelected
- send: #onMethodSelected:
- to: self;
-
- on: HLClassSelected
- send: #onClassSelected:
- to: self;
-
- on: HLPackageSelected
- send: #onPackageSelected:
- to: self;
-
- on: HLProtocolSelected
- send: #onProtocolSelected:
- to: self;
-
- on: HLSourceCodeFocusRequested
- send: #onSourceCodeFocusRequested
- to: self;
-
- on: HLShowTemplate
- send: #onShowTemplate:
- to: self
- !
- observeSystem
- self browserModel systemAnnouncer
- on: MethodModified
- send: #onMethodModified:
- to: self
- !
- refresh
- self hasModification ifTrue: [ ^ self ].
- self hasFocus ifTrue: [ ^ self ].
- self contents: self browserModel selectedMethod source
- !
- renderButtonsOn: html
- html button
- class: 'button';
- with: 'SaveIt';
- onClick: [ self saveIt ].
- super renderButtonsOn: html
- !
- saveIt
- self browserModel saveSourceCode
- !
- unregister
- super unregsiter.
-
- self browserModel announcer unsubscribe: self.
- self browserModel systemAnnouncer unsubscribe: self
- ! !
- !HLBrowserCodeWidget methodsFor: 'reactions'!
- onBrowserAboutToChange: anAnnouncement
- | block |
-
- block := anAnnouncement actionBlock.
-
- self hasModification
- ifTrue: [
- self
- confirm: 'Changes have not been saved. Do you want to discard these changes?'
- ifTrue: [
- "Don't ask twice"
- self methodContents: self contents.
- block value ].
-
-
- HLChangeForbidden signal ]
- !
- onClassSelected: anAnnouncement
- | class |
-
- class:= anAnnouncement item.
-
- class ifNil: [ ^ self contents: '' ].
- self contents: class definition
- !
- onCompileError: anAnnouncement
- Terminal alert: anAnnouncement error messageText
- !
- onInstVarAdded
- self browserModel save: self contents
- !
- onMethodModified: anAnnouncement
- | method |
-
- method := anAnnouncement method.
-
- self browserModel selectedClass = method methodClass ifFalse: [ ^ self ].
- self browserModel selectedMethod ifNil: [ ^ self ].
- self browserModel selectedMethod selector = method selector ifFalse: [ ^ self ].
- self refresh
- !
- onMethodSelected: anAnnouncement
- | method |
-
- method := anAnnouncement item.
-
- method ifNil: [ ^ self contents: '' ].
- self contents: method source
- !
- onPackageSelected: anAnnouncement
- | package |
-
- package := anAnnouncement item.
-
- package ifNil: [ ^ self contents: '' ].
- self contents: package definition
- !
- onParseError: anAnnouncement
- | lineIndex newContents |
-
- lineIndex := 1.
-
- self contents: (String streamContents: [ :stream |
- self contents linesDo: [ :each |
- lineIndex = anAnnouncement line
- ifTrue: [
- stream
- nextPutAll: (each copyFrom: 1 to: anAnnouncement column);
- nextPutAll: '<- ';
- nextPutAll: anAnnouncement message;
- nextPutAll: ' ';
- nextPutAll: (each copyFrom: anAnnouncement column + 1 to: each size) ]
- ifFalse: [ stream nextPutAll: each ].
- stream nextPutAll: String cr.
- lineIndex := lineIndex + 1 ] ])
- !
- onProtocolSelected: anAnnouncement
- self browserModel selectedClass ifNil: [ ^ self contents: '' ].
- self contents: self browserModel selectedClass definition
- !
- onSaveIt
- self browserModel save: self contents
- !
- onShowInstanceToggled
- self browserModel selectedClass ifNil: [ ^ self contents: '' ].
-
- self contents: self browserModel selectedClass definition
- !
- onShowTemplate: anAnnouncement
- self contents: anAnnouncement template
- !
- onSourceCodeFocusRequested
- self focus
- !
- onSourceCodeSaved
- self methodContents: self contents.
- self updateState
- !
- onUnknownVariableError: anAnnouncement
- | error |
-
- error := anAnnouncement error.
-
- self
- confirm: (String streamContents: [ :stream |
- stream
- nextPutAll: error messageText;
- nextPutAll: String cr;
- nextPutAll: 'Would you like to define an instance variable?' ])
- ifTrue: [
- self browserModel addInstVarNamed: error variableName ]
- ! !
- !HLBrowserCodeWidget class methodsFor: 'instance creation'!
- on: aBrowserModel
- ^ self new
- browserModel: aBrowserModel;
- yourself
- ! !
- !HLBrowserCodeWidget class methodsFor: 'testing'!
- canBeOpenAsTab
- ^ false
- ! !
- HLWidget subclass: #HLWorkspace
- slots: {#codeWidget. #transcript}
- package: 'Helios-Workspace'!
- !HLWorkspace methodsFor: 'accessing'!
- codeWidget
- ^ codeWidget ifNil: [ codeWidget := HLCodeWidget new ]
- !
- transcript
- ^ transcript ifNil: [ transcript := HLTranscript new ]
- ! !
- !HLWorkspace methodsFor: 'actions'!
- focus
- ^ self codeWidget focus
- !
- unregister
- super unregister.
- self transcript unregister
- ! !
- !HLWorkspace methodsFor: 'rendering'!
- renderContentOn: html
- html with: (HLContainer with: (HLHorizontalSplitter
- with: self codeWidget
- with: [ :canvas | self renderTranscriptOn: canvas ]))
- !
- renderTranscriptOn: html
- html div
- class: 'transcript-container';
- with: [
- html div
- class: 'list-label';
- with: 'Transcript'.
- self transcript renderOn: html ]
- ! !
- !HLWorkspace methodsFor: 'testing'!
- canHaveFocus
- ^ true
- ! !
- !HLWorkspace class methodsFor: 'accessing'!
- tabClass
- ^ 'workspace'
- !
- tabLabel
- ^ 'Workspace'
- !
- tabPriority
- ^ 10
- ! !
- !HLWorkspace class methodsFor: 'testing'!
- canBeOpenAsTab
- ^ true
- ! !
|