Helios-Core.st 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. Smalltalk current createPackage: 'Helios-Core' properties: #{}!
  2. Object subclass: #HLEnvironment
  3. instanceVariableNames: ''
  4. package: 'Helios-Core'!
  5. !HLEnvironment commentStamp!
  6. Abstract class defining common behavior for local and remote environments!
  7. !HLEnvironment methodsFor: 'accessing'!
  8. packages
  9. ^ self subclassResponsibility
  10. ! !
  11. !HLEnvironment methodsFor: 'actions'!
  12. eval: someCode on: aReceiver
  13. ^ self subclassResponsibility
  14. ! !
  15. HLEnvironment subclass: #HLLocalEnvironment
  16. instanceVariableNames: ''
  17. package: 'Helios-Core'!
  18. !HLLocalEnvironment methodsFor: 'accessing'!
  19. packages
  20. ^ Smalltalk current packages
  21. ! !
  22. !HLLocalEnvironment methodsFor: 'actions'!
  23. eval: someCode on: aReceiver
  24. | compiler |
  25. compiler := Compiler new.
  26. [compiler parseExpression: someCode] on: Error do: [:ex |
  27. ^window alert: ex messageText].
  28. ^(compiler eval: (compiler compile: 'doIt ^[', someCode, '] value' forClass: DoIt)) fn applyTo: aReceiver arguments: #()
  29. ! !
  30. HLEnvironment subclass: #HLRemoveEnvironment
  31. instanceVariableNames: ''
  32. package: 'Helios-Core'!
  33. !HLRemoveEnvironment methodsFor: 'accessing'!
  34. packages
  35. "Answer the remote environment's packages"
  36. "to-do"
  37. "Note for future self and friends:
  38. the problem with remote stuff is that the answers shouldn't be expected to be
  39. received in a syncrhonous fashion. Everything network is asyc, so you *are going to deal with callbacks* here"
  40. ! !
  41. Widget subclass: #HLSourceArea
  42. instanceVariableNames: 'editor textarea div receiver onDoIt'
  43. package: 'Helios-Core'!
  44. !HLSourceArea methodsFor: 'accessing'!
  45. contents
  46. ^editor getValue
  47. !
  48. contents: aString
  49. editor setValue: aString
  50. !
  51. currentLine
  52. ^editor getLine: (editor getCursor line)
  53. !
  54. currentLineOrSelection
  55. ^editor somethingSelected
  56. ifFalse: [self currentLine]
  57. ifTrue: [self selection]
  58. !
  59. editor
  60. ^editor
  61. !
  62. onDoIt
  63. ^onDoIt
  64. !
  65. onDoIt: aBlock
  66. onDoIt := aBlock
  67. !
  68. receiver
  69. ^receiver ifNil: [DoIt new]
  70. !
  71. receiver: anObject
  72. receiver := anObject
  73. !
  74. selection
  75. ^editor getSelection
  76. !
  77. selectionEnd
  78. ^textarea element selectionEnd
  79. !
  80. selectionEnd: anInteger
  81. textarea element selectionEnd: anInteger
  82. !
  83. selectionStart
  84. ^textarea element selectionStart
  85. !
  86. selectionStart: anInteger
  87. textarea element selectionStart: anInteger
  88. !
  89. setEditorOn: aTextarea
  90. <self['@editor'] = CodeMirror.fromTextArea(aTextarea, {
  91. theme: 'amber',
  92. lineNumbers: true,
  93. enterMode: 'flat',
  94. matchBrackets: true,
  95. electricChars: false
  96. })>
  97. !
  98. val
  99. ^editor getValue
  100. !
  101. val: aString
  102. editor setValue: aString
  103. ! !
  104. !HLSourceArea methodsFor: 'actions'!
  105. clear
  106. self contents: ''
  107. !
  108. doIt
  109. | result |
  110. result := self eval: self currentLineOrSelection.
  111. self onDoIt ifNotNil: [self onDoIt value].
  112. ^result
  113. !
  114. eval: aString
  115. | compiler |
  116. compiler := Compiler new.
  117. [compiler parseExpression: aString] on: Error do: [:ex |
  118. ^window alert: ex messageText].
  119. ^(compiler eval: (compiler compile: 'doIt ^[', aString, '] value' forClass: DoIt)) fn applyTo: self receiver arguments: #()
  120. !
  121. fileIn
  122. Importer new import: self currentLineOrSelection readStream
  123. !
  124. handleKeyDown: anEvent
  125. <if(anEvent.ctrlKey) {
  126. if(anEvent.keyCode === 80) { //ctrl+p
  127. self._printIt();
  128. anEvent.preventDefault();
  129. return false;
  130. }
  131. if(anEvent.keyCode === 68) { //ctrl+d
  132. self._doIt();
  133. anEvent.preventDefault();
  134. return false;
  135. }
  136. if(anEvent.keyCode === 73) { //ctrl+i
  137. self._inspectIt();
  138. anEvent.preventDefault();
  139. return false;
  140. }
  141. }>
  142. !
  143. inspectIt
  144. self doIt inspect
  145. !
  146. print: aString
  147. | start stop |
  148. start := HashedCollection new.
  149. stop := HashedCollection new.
  150. start at: 'line' put: (editor getCursor: false) line.
  151. start at: 'ch' put: (editor getCursor: false) ch.
  152. stop at: 'line' put: (start at: 'line').
  153. stop at: 'ch' put: ((start at: 'ch') + aString size + 2).
  154. editor replaceSelection: (editor getSelection, ' ', aString, ' ').
  155. editor setCursor: (editor getCursor: true).
  156. editor setSelection: stop end: start
  157. !
  158. printIt
  159. self print: self doIt printString
  160. ! !
  161. !HLSourceArea methodsFor: 'events'!
  162. onKeyDown: aBlock
  163. div onKeyDown: aBlock
  164. !
  165. onKeyUp: aBlock
  166. div onKeyUp: aBlock
  167. ! !
  168. !HLSourceArea methodsFor: 'rendering'!
  169. renderOn: html
  170. div := html div class: 'source'.
  171. div with: [textarea := html textarea].
  172. self setEditorOn: textarea element.
  173. div onKeyDown: [:e | self handleKeyDown: e]
  174. ! !
  175. Object subclass: #HLTab
  176. instanceVariableNames: 'widget label'
  177. package: 'Helios-Core'!
  178. !HLTab methodsFor: 'accessing'!
  179. activate
  180. self manager activate: self
  181. !
  182. add
  183. self manager addTab: self
  184. !
  185. label
  186. ^ label ifNil: [ '' ]
  187. !
  188. label: aString
  189. label := aString
  190. !
  191. manager
  192. ^ HLManager current
  193. !
  194. widget
  195. ^ widget
  196. !
  197. widget: aWidget
  198. widget := aWidget
  199. ! !
  200. !HLTab methodsFor: 'testing'!
  201. isActive
  202. ^ self manager activeTab = self
  203. ! !
  204. !HLTab class methodsFor: 'instance creation'!
  205. on: aWidget labelled: aString
  206. ^ self new
  207. widget: aWidget;
  208. label: aString;
  209. yourself
  210. ! !
  211. Widget subclass: #HLWidget
  212. instanceVariableNames: 'rootDiv'
  213. package: 'Helios-Core'!
  214. !HLWidget methodsFor: 'accessing'!
  215. manager
  216. ^ HLManager current
  217. ! !
  218. !HLWidget methodsFor: 'announcements'!
  219. subscribeTo: anAnnouncer
  220. ! !
  221. !HLWidget methodsFor: 'keybindings'!
  222. registerBindings
  223. self registerBindingsOn: self manager keyBinder bindings
  224. !
  225. registerBindingsOn: aBindingGroup
  226. ! !
  227. !HLWidget methodsFor: 'rendering'!
  228. renderContentOn: html
  229. !
  230. renderOn: html
  231. self registerBindings.
  232. rootDiv := html div with: [
  233. self renderContentOn: html ]
  234. ! !
  235. !HLWidget methodsFor: 'updating'!
  236. refresh
  237. rootDiv ifNil: [ ^ self ].
  238. rootDiv asJQuery empty.
  239. [ :html | self renderContentOn: html ] appendToJQuery: rootDiv asJQuery
  240. ! !
  241. !HLWidget class methodsFor: 'accessing'!
  242. openAsTab
  243. HLManager current addTab: (HLTab on: self new labelled: self tabLabel)
  244. !
  245. tabLabel
  246. ^ 'Tab'
  247. !
  248. tabPriority
  249. ^ 500
  250. ! !
  251. !HLWidget class methodsFor: 'testing'!
  252. canBeOpenAsTab
  253. ^ false
  254. ! !
  255. HLWidget subclass: #HLDebugger
  256. instanceVariableNames: ''
  257. package: 'Helios-Core'!
  258. HLWidget subclass: #HLFocusableWidget
  259. instanceVariableNames: 'hiddenInput'
  260. package: 'Helios-Core'!
  261. !HLFocusableWidget methodsFor: 'accessing'!
  262. focusClass
  263. ^ 'focused'
  264. ! !
  265. !HLFocusableWidget methodsFor: 'events'!
  266. blur
  267. hiddenInput asJQuery blur
  268. !
  269. focus
  270. hiddenInput asJQuery focus
  271. !
  272. hasFocus
  273. ^ rootDiv notNil and: [ rootDiv asJQuery hasClass: self focusClass ]
  274. ! !
  275. !HLFocusableWidget methodsFor: 'rendering'!
  276. renderContentOn: html
  277. !
  278. renderHiddenInputOn: html
  279. hiddenInput := html input
  280. style: 'position: absolute; left: -100000px;';
  281. onBlur: [ rootDiv asJQuery removeClass: self focusClass ];
  282. onFocus: [ rootDiv asJQuery addClass: self focusClass ]
  283. !
  284. renderOn: html
  285. self registerBindings.
  286. self renderHiddenInputOn: html.
  287. rootDiv := html div
  288. class: 'hl_widget';
  289. onClick: [ hiddenInput asJQuery focus ];
  290. with: [
  291. self renderContentOn: html ]
  292. ! !
  293. HLFocusableWidget subclass: #HLListWidget
  294. instanceVariableNames: 'items selectedItem'
  295. package: 'Helios-Core'!
  296. !HLListWidget methodsFor: 'accessing'!
  297. cssClassForItem: anObject
  298. ^ self selectedItem = anObject
  299. ifTrue: [ 'active' ]
  300. ifFalse: [ 'inactive' ]
  301. !
  302. iconForItem: anObject
  303. ^ ''
  304. !
  305. items
  306. ^ items ifNil: [ #() ]
  307. !
  308. items: aCollection
  309. items := aCollection
  310. !
  311. selectedItem
  312. ^ selectedItem
  313. !
  314. selectedItem: anObject
  315. selectedItem := anObject
  316. ! !
  317. !HLListWidget methodsFor: 'actions'!
  318. activateListItem: aListItem
  319. | parent position |
  320. (aListItem get: 0) ifNil: [ ^self ].
  321. <position = aListItem.parent().children().get().indexOf(aListItem.get(0)) + 1>.
  322. parent := aListItem parent.
  323. parent children removeClass: 'active'.
  324. aListItem addClass: 'active'.
  325. "Move the scrollbar to show the active element"
  326. aListItem position top < 0 ifTrue: [
  327. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].
  328. aListItem position top + aListItem height > parent height ifTrue: [
  329. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ].
  330. "Activate the corresponding item"
  331. self selectItem: (self items at: (aListItem attr: 'list-data') asNumber)
  332. !
  333. selectItem: anObject
  334. self selectedItem: anObject
  335. ! !
  336. !HLListWidget methodsFor: 'events'!
  337. setupKeyBindings
  338. | next |
  339. hiddenInput asJQuery unbind: 'keydown'.
  340. hiddenInput asJQuery keydown: [ :e | | selected |
  341. selected := window jQuery: '.focused .nav-pills .active'.
  342. e which = 38 ifTrue: [
  343. self activateListItem: selected prev ].
  344. e which = 40 ifTrue: [
  345. next := selected next.
  346. (next get: 0) ifNil: [ next := window jQuery: '.focused .nav-pills li:first-child' ].
  347. self activateListItem: next ] ]
  348. ! !
  349. !HLListWidget methodsFor: 'rendering'!
  350. renderButtonsOn: html
  351. !
  352. renderContentOn: html
  353. html ul
  354. class: 'nav nav-pills nav-stacked';
  355. with: [ self renderListOn: html ].
  356. html div class: 'pane_actions form-actions'; with: [
  357. self renderButtonsOn: html ].
  358. self setupKeyBindings
  359. !
  360. renderItem: anObject on: html
  361. | li |
  362. li := html li.
  363. li
  364. class: (self cssClassForItem: anObject);
  365. at: 'list-data' put: (self items indexOf: anObject) asString;
  366. with: [
  367. html a
  368. with: [
  369. (html tag: 'i') class: (self iconForItem: anObject).
  370. self renderItemLabel: anObject on: html ];
  371. onClick: [
  372. self activateListItem: li asJQuery ] ]
  373. !
  374. renderItemLabel: anObject on: html
  375. html with: anObject asString
  376. !
  377. renderListOn: html
  378. self items do: [ :each |
  379. self renderItem: each on: html ]
  380. ! !
  381. HLListWidget subclass: #HLNavigationListWidget
  382. instanceVariableNames: 'previous next'
  383. package: 'Helios-Core'!
  384. !HLNavigationListWidget methodsFor: 'accessing'!
  385. next
  386. ^ next
  387. !
  388. next: aWidget
  389. next := aWidget.
  390. aWidget previous = self ifFalse: [ aWidget previous: self ]
  391. !
  392. previous
  393. ^ previous
  394. !
  395. previous: aWidget
  396. previous := aWidget.
  397. aWidget next = self ifFalse: [ aWidget next: self ]
  398. ! !
  399. !HLNavigationListWidget methodsFor: 'actions'!
  400. nextFocus
  401. self next ifNotNil: [ self next focus ]
  402. !
  403. previousFocus
  404. self previous ifNotNil: [ self previous focus ]
  405. ! !
  406. !HLNavigationListWidget methodsFor: 'events'!
  407. setupKeyBindings
  408. super setupKeyBindings.
  409. hiddenInput asJQuery keydown: [ :e |
  410. e which = 39 ifTrue: [
  411. self nextFocus ].
  412. e which = 37 ifTrue: [
  413. self previousFocus ] ]
  414. ! !
  415. HLWidget subclass: #HLManager
  416. instanceVariableNames: 'tabs activeTab keyBinder'
  417. package: 'Helios-Core'!
  418. !HLManager methodsFor: 'accessing'!
  419. activeTab
  420. ^ activeTab
  421. !
  422. keyBinder
  423. ^ keyBinder ifNil: [ keyBinder := HLKeyBinder new ]
  424. !
  425. tabs
  426. ^ tabs ifNil: [ tabs := OrderedCollection new ]
  427. ! !
  428. !HLManager methodsFor: 'actions'!
  429. activate: aTab
  430. self keyBinder flushBindings.
  431. activeTab := aTab.
  432. self
  433. refresh;
  434. show: aTab
  435. !
  436. addTab: aTab
  437. self tabs add: aTab.
  438. self activate: aTab
  439. !
  440. removeTab: aTab
  441. "Todo: activate the previously activated tab. Keep a history of tabs selection"
  442. (self tabs includes: aTab) ifFalse: [ ^ self ].
  443. self tabs remove: aTab.
  444. self refresh
  445. ! !
  446. !HLManager methodsFor: 'initialization'!
  447. initialize
  448. super initialize.
  449. self keyBinder setupEvents
  450. ! !
  451. !HLManager methodsFor: 'rendering'!
  452. refresh
  453. (window jQuery: '.navbar') remove.
  454. (window jQuery: '#container') remove.
  455. self appendToJQuery: 'body' asJQuery
  456. !
  457. renderAddOn: html
  458. html li
  459. class: 'dropdown';
  460. with: [
  461. html a
  462. class: 'dropdown-toggle';
  463. at: 'data-toggle' put: 'dropdown';
  464. with: [
  465. html with: 'Open...'.
  466. (html tag: 'b') class: 'caret' ].
  467. html ul
  468. class: 'dropdown-menu';
  469. with: [
  470. ((HLWidget withAllSubclasses
  471. select: [ :each | each canBeOpenAsTab ])
  472. sorted: [ :a :b | a tabPriority < b tabPriority ])
  473. do: [ :each |
  474. html li with: [
  475. html a
  476. with: each tabLabel;
  477. onClick: [ each openAsTab ] ] ] ] ]
  478. !
  479. renderContentOn: html
  480. html div
  481. class: 'navbar navbar-fixed-top';
  482. with: [ html div
  483. class: 'navbar-inner';
  484. with: [ self renderTabsOn: html ] ].
  485. html div id: 'container'
  486. !
  487. renderTabsOn: html
  488. html ul
  489. class: 'nav';
  490. with: [
  491. self tabs do: [ :each |
  492. html li
  493. class: (each isActive ifTrue: [ 'active' ] ifFalse: [ 'inactive' ]);
  494. with: [
  495. html a
  496. with: [
  497. ((html tag: 'i') class: 'icon-remove-circle')
  498. onClick: [ self removeTab: each ].
  499. html with: each label ];
  500. onClick: [ each activate ] ] ].
  501. self renderAddOn: html ]
  502. !
  503. show: aTab
  504. (window jQuery: '#container') empty.
  505. aTab widget appendToJQuery: '#container' asJQuery
  506. ! !
  507. HLManager class instanceVariableNames: 'current'!
  508. !HLManager class methodsFor: 'accessing'!
  509. current
  510. ^ current ifNil: [ current := self basicNew initialize ]
  511. ! !
  512. !HLManager class methodsFor: 'initialization'!
  513. initialize
  514. self current appendToJQuery: 'body' asJQuery
  515. ! !
  516. !HLManager class methodsFor: 'instance creation'!
  517. new
  518. "Use current instead"
  519. self shouldNotImplement
  520. ! !
  521. HLWidget subclass: #HLSUnit
  522. instanceVariableNames: ''
  523. package: 'Helios-Core'!
  524. !HLSUnit class methodsFor: 'accessing'!
  525. tabLabel
  526. ^ 'SUnit'
  527. !
  528. tabPriority
  529. ^ 1000
  530. ! !
  531. !HLSUnit class methodsFor: 'testing'!
  532. canBeOpenAsTab
  533. ^ true
  534. ! !
  535. HLWidget subclass: #HLTranscript
  536. instanceVariableNames: ''
  537. package: 'Helios-Core'!
  538. !HLTranscript class methodsFor: 'accessing'!
  539. tabLabel
  540. ^ 'Transcript'
  541. !
  542. tabPriority
  543. ^ 600
  544. ! !
  545. !HLTranscript class methodsFor: 'testing'!
  546. canBeOpenAsTab
  547. ^ true
  548. ! !