diff --git a/packages/DomainCode-Parser/DCBlock.class.st b/packages/DomainCode-Parser/DCBlock.class.st index afc3400..cea1785 100644 --- a/packages/DomainCode-Parser/DCBlock.class.st +++ b/packages/DomainCode-Parser/DCBlock.class.st @@ -257,7 +257,7 @@ DCBlock class >> smalltalkCollapseBlocks [ ^ { [:x | x language = SBTSSmalltalk]. [:x | x type = #block]. - [:x | x sourceString size > 400]. + [:x | x sourceString size > 3000]. [:x | x installPassiveReplacement: DCCollapsed new]} ] @@ -271,6 +271,16 @@ DCBlock class >> smalltalkCollapseBlocksOnDoubleClick [ [:x | x installPassiveReplacement: DCCollapsed new]} ] +{ #category : #'smalltalk - helpers' } +DCBlock class >> smalltalkCreateClassFor: aBlock [ + + aBlock sandblockEditor do: (SBStCreateClassCommand new + name: aBlock contents; + source: aBlock; + editor: aBlock sandblockEditor; + artefact: aBlock containingArtefact) +] + { #category : #smalltalk } DCBlock class >> smalltalkCreateNewMethod [ @@ -323,6 +333,78 @@ DCBlock class >> smalltalkDeclaration [ {#args. id. decl}]} ] +{ #category : #'smalltalk - helpers' } +DCBlock class >> smalltalkDeclareBlockLocal: aBlock [ + + | block decl | + decl := DCBlock new + type: #identifier; + addMorphBack: (DCText new contents: aBlock contents). + block := aBlock orAnyParent: {#block. #method}. + block childSandblocks + detect: [:b | b type = #temporaries] + ifFound: [:temporaries | + aBlock sandblockEditor do: (SBInsertCommand new + morph: decl; + container: temporaries; + index: temporaries submorphCount)] + ifNone: [ | index | + index := block children findFirst: [:s | s treeLabel = '|']. + aBlock sandblockEditor do: (SBInsertCommand new + morph: (DCBlock new + type: #temporaries; + addMorphBack: (DCText new contents: '|'); + addMorphBack: decl; + addMorphBack: (DCText new contents: '|')); + index: (index = 0 ifTrue: [2] ifFalse: [index + 1]); + container: block)] +] + +{ #category : #'smalltalk - helpers' } +DCBlock class >> smalltalkDeclareClassVariable: aBlock [ + + aBlock sandblockEditor do: (SBStDeclareClassVarCommand new + class: aBlock containingArtefact methodClass theNonMetaClass; + name: aBlock contents; + source: aBlock) +] + +{ #category : #'smalltalk - helpers' } +DCBlock class >> smalltalkDeclareInstanceVariable: aBlock [ + + aBlock sandblockEditor do: (SBStDeclareInstVarCommand new + class: aBlock containingArtefact methodClass; + name: aBlock contents; + source: aBlock) +] + +{ #category : #'smalltalk - helpers' } +DCBlock class >> smalltalkDeclareTemporary: aBlock [ + + | block decl | + decl := DCBlock new + type: #identifier; + addMorphBack: (DCText new contents: aBlock contents). + block := aBlock orAnyParent: {#method}. + block childSandblocks + detect: [:b | b type = #temporaries] + ifFound: [:temporaries | + aBlock sandblockEditor do: (SBInsertCommand new + morph: decl; + container: temporaries; + index: temporaries submorphCount)] + ifNone: [ | index | + index := block children findFirst: [:s | s treeLabel = '|']. + aBlock sandblockEditor do: (SBInsertCommand new + morph: (DCBlock new + type: #temporaries; + addMorphBack: (DCText new contents: '|'); + addMorphBack: decl; + addMorphBack: (DCText new contents: '|')); + index: (index = 0 ifTrue: [2] ifFalse: [index + 1]); + container: block)] +] + { #category : #smalltalk } DCBlock class >> smalltalkFlagMarker [ @@ -398,13 +480,11 @@ DCBlock class >> smalltalkInsertArg [ #(#First #Second #Third #Fourth #Fifith) withIndexDo: [:name :index | x registerShortcut: #use, name, #Argument do: [ { - [:block | | id | - id := DCQuery - script: {self smalltalkMethodArguments. [:args | (args at: index) contents]} - with: block. - (block type = #identifier or: [block sandblockEditor mode = #command]) - ifTrue: [block replaceWith: id, ' '] - ifFalse: [block insert: id, ' ']]}]]]} + [:block | + (DCQuery script: {self smalltalkMethodArguments. [:args | (args at: index) contents]} with: block) ifNotNil: [:id | + (block type = #identifier or: [block sandblockEditor mode = #command]) + ifTrue: [block replaceWith: id, ' '] + ifFalse: [block insert: id, ' ']]]}]]]} ] { #category : #smalltalk } @@ -448,13 +528,19 @@ DCBlock class >> smalltalkMessageSendAutoCompletion [ ^ { [:x | x isSelected]. + [:x | "only autocomplete for the first message part (just after receiver)" + x siblingIndex = 2]. [:x | (DCQuery script: self smalltalkMessageSendSelector with: x) ifNotNil: [:res | res, {x}]]. [:selector :message :part | part addSuggestions: ((self sortedSuggestions: Symbol allSymbols for: selector addAll: false max: 10) collect: [:sel | DCSuggestionItem new selector: sel label: 'send' source: ((sel allSatisfy: #isSpecial) ifTrue: [sel, ' __sb'] ifFalse: [ (sel includes: $:) - ifTrue: [((sel splitBy: ':') allButLast collect: [:p | p, ': __sb']) joinSeparatedBy: ' '] + ifTrue: [ + ((sel splitBy: ':') allButLast collectWithIndex: [:p :index | + p, ((index = 1 and: [part nextBlock ifNotNil: #isExpression ifNil: [false]]) + ifTrue: [':'] + ifFalse: [': __sb'])]) joinSeparatedBy: ' '] ifFalse: [sel]]); completionAction: [:editor | editor selection parent @@ -492,7 +578,7 @@ DCBlock class >> smalltalkMethodSelector [ ifNone: [nil]]} ] -{ #category : #nil } +{ #category : #smalltalk } DCBlock class >> smalltalkRunTest [ @@ -536,6 +622,25 @@ DCBlock class >> smalltalkSelector [ with: x]} ] +{ #category : #'smalltalk - helpers' } +DCBlock class >> smalltalkSelectorWithPlaceholders: aString [ + + (aString allSatisfy: #isSpecial) ifTrue: [^ aString, ' __sb']. + (aString includes: $:) ifFalse: [^ aString]. + ^ ((aString splitBy: ':') allButLast collect: [:part | part, ': __sb']) joinSeparatedBy: ' ' +] + +{ #category : #smalltalk } +DCBlock class >> smalltalkSymbolAutocompletion [ + + + ^ { + [:x | x isSelected]. + [:x | x language = SBTSSmalltalk]. + [:x | x type = #symbol]. + [:x | x addSuggestions: ((self sortedSuggestions: Symbol allSymbols for: x contents allButFirst addAll: false max: 10) collect: [:sel | DCSuggestionItem new selector: sel label: 'symbol' source: sel])]} +] + { #category : #smalltalk } DCBlock class >> smalltalkToggleBoolean [ @@ -560,29 +665,57 @@ DCBlock class >> smalltalkUndeclaredVariable [ [:x | x reportError: (SBErrorDecorator new message: 'undeclared variable'; + fixActions: (x contents first isUppercase + ifTrue: [ + { + SBCodeAction + labeled: 'Declare class var' + for: x + do: [:block | self smalltalkDeclareClassVariable: block]. + SBCodeAction + labeled: 'Create class ', x contents + for: x + do: [:block | self smalltalkCreateClassFor: block]}] + ifFalse: [ + { + SBCodeAction + labeled: 'Declare block-local' + for: x + do: [:block | self smalltalkDeclareBlockLocal: block]. + SBCodeAction + labeled: 'Declare method temporary' + for: x + do: [:block | self smalltalkDeclareTemporary block]. + SBCodeAction + labeled: 'Declare instance variable' + for: x + do: [:block | self smalltalkDeclareInstanceVariable: block]}]))]} +] + +{ #category : #smalltalk } +DCBlock class >> smalltalkUnknownSelector [ + + + ^ { + self smalltalkMessageSendSelector. + [:selector :message | (Symbol lookup: selector) isNil]. + [:selector :message | + message children second reportError: (SBErrorDecorator new + message: 'unknown message'; fixActions: { - SBCodeAction labeled: 'Declare block-local' for: x do: [:node | | block decl | - decl := DCBlock new - type: #identifier; - addMorphBack: (DCText new contents: node contents). - block := node orAnyParent: {#block. #method}. - block childSandblocks - detect: [:b | b type = #temporaries] - ifFound: [:temporaries | - node sandblockEditor do: (SBInsertCommand new - morph: decl; - container: temporaries; - index: temporaries submorphCount)] - ifNone: [ | index | - index := block children findFirst: [:s | s treeLabel = '|']. - node sandblockEditor do: (SBInsertCommand new - morph: (DCBlock new - type: #temporaries; - addMorphBack: (DCText new contents: '|'); - addMorphBack: decl; - addMorphBack: (DCText new contents: '|')); - index: (index = 0 ifTrue: [2] ifFalse: [index + 1]); - container: block)]]})]} + SBCodeAction labeled: 'Create method on ...' for: message do: [:node | | method class | + class := UIManager default chooseClassOrTrait. + class ifNotNil: [ + self halt. + method := DCSmalltalkMethod + newWith: (self smalltalkSelectorWithPlaceholders: selector) + in: class. + node sandblockEditor do: (SBEditorOpenMorphCommand new + morph: method; + editor: node sandblockEditor; + isUnsaved: true; + yourself)]]. + SBCodeAction labeled: 'Confirm selector' for: message do: [:node | node selector asSymbol]})]} ] { #category : #smalltalk } @@ -638,7 +771,7 @@ DCBlock class >> smalltalkWatch [ expr := message children third. { expr type = 'parenthesized_expression' - ifTrue: [expr children first] + ifTrue: [expr children second] ifFalse: [expr]}] initDo: [:w :expr | w addMorphBack: expr]]} ] @@ -1213,6 +1346,14 @@ DCBlock >> isBlockBody [ ^ self language ifNotNil: [:l | l blockBodyTypes includes: self type] ifNil: [false] ] +{ #category : #'as yet unclassified' } +DCBlock >> isExpression [ + + (self type = #ERROR and: [self submorphCount = 1]) ifTrue: [^ self firstSubmorph isExpression]. + + ^ self language expressionTypes anySatisfy: [:type | self language instance grammar is: self type subtypeOf: type] +] + { #category : #hierarchy } DCBlock >> isOrHasParent: aCollectionOrSymbol [ @@ -1503,6 +1644,8 @@ DCBlock >> printTreeOn: aStream indent: aNumber [ { #category : #'as yet unclassified' } DCBlock >> queryAll: aString [ + self assert: (aString includes: $@) description: 'query needs a capture (@) to be useful'. + ^ Array streamContents: [:stream | self allChildrenDo: [:block | block isTSMorph ifTrue: [ diff --git a/packages/DomainCode-Parser/DCEditTest.class.st b/packages/DomainCode-Parser/DCEditTest.class.st index 8acb875..00a2153 100644 --- a/packages/DomainCode-Parser/DCEditTest.class.st +++ b/packages/DomainCode-Parser/DCEditTest.class.st @@ -62,6 +62,44 @@ c.' language: SBTSSmalltalk. c .' equals: editor childSandblocks first sourceString ] +{ #category : #'as yet unclassified' } +DCEditTest >> testSmalltalkEditMessageWithAutoCompl [ + + | block editor | + block := DCSmalltalkMethod newWith: 'a + +a with: 3' in: self class. + editor := self editorAndWorldFor: block. + (block method queryAll: '(keyword) @') first + select; + clearInput. + self type: 'with' in: editor. + self tick. + editor handle: (SBTest keyboardEvent: Character tab). + self assert: 'a + +a with: 3' equals: block method sourceString +] + +{ #category : #'as yet unclassified' } +DCEditTest >> testSmalltalkFillPlaceholderWithArg [ + + | block editor | + block := DCSmalltalkMethod newWith: 'a: arg + +a' in: self class. + editor := self editorAndWorldFor: block. + block lastDeepChild startInputAtEnd. + self type: ' with' in: editor. + self tick. + editor handle: (SBTest keyboardEvent: Character tab). + self tick. + editor handle: (SBTest keyboardEvent: $1 shift: false command: true). + self assert: 'a: arg + +a with: arg' equals: block method sourceString +] + { #category : #'as yet unclassified' } DCEditTest >> testSmalltalkSwapStatementsWithEmpty [ diff --git a/packages/DomainCode-Parser/DCJumpPlaceholder.class.st b/packages/DomainCode-Parser/DCJumpPlaceholder.class.st index 4a09519..3253d6d 100644 --- a/packages/DomainCode-Parser/DCJumpPlaceholder.class.st +++ b/packages/DomainCode-Parser/DCJumpPlaceholder.class.st @@ -44,6 +44,12 @@ DCJumpPlaceholder >> pasteReplace [ do: [:new :edit | self applyEdit: edit source: new cursorAt: cursorIndex + str size]] ] +{ #category : #'as yet unclassified' } +DCJumpPlaceholder >> type [ + + ^ #identifier +] + { #category : #'as yet unclassified' } DCJumpPlaceholder >> valid [ diff --git a/packages/DomainCode-Parser/DCQueryState.class.st b/packages/DomainCode-Parser/DCQueryState.class.st index 534d05e..6e6e253 100644 --- a/packages/DomainCode-Parser/DCQueryState.class.st +++ b/packages/DomainCode-Parser/DCQueryState.class.st @@ -122,7 +122,9 @@ DCQueryState >> queueUpdateQueriesFor: aSymbol [ { #category : #'as yet unclassified' } DCQueryState >> reportError: aDecorator for: aBlock [ - (newDecorators at: aBlock ifAbsentPut: [OrderedCollection new]) add: aDecorator + | errors | + errors := newDecorators at: aBlock ifAbsentPut: [OrderedCollection new]. + (errors noneSatisfy: [:e | e message = aDecorator message]) ifTrue: [errors add: aDecorator] ] { #category : #'as yet unclassified' } diff --git a/packages/DomainCode-Parser/DCSmalltalkMethod.class.st b/packages/DomainCode-Parser/DCSmalltalkMethod.class.st index 60756bd..2febc16 100644 --- a/packages/DomainCode-Parser/DCSmalltalkMethod.class.st +++ b/packages/DomainCode-Parser/DCSmalltalkMethod.class.st @@ -19,6 +19,12 @@ DCSmalltalkMethod class >> for: aCompiledMethod [ ^ aCompiledMethod isSandblock ifFalse: [self new for: aCompiledMethod] ifTrue: [self new emptyIn: aCompiledMethod methodClass] ] +{ #category : #'as yet unclassified' } +DCSmalltalkMethod class >> newWith: aString in: aClass [ + + ^ self new in: aClass with: aString +] + { #category : #'as yet unclassified' } DCSmalltalkMethod >> browse [ @@ -26,6 +32,12 @@ DCSmalltalkMethod >> browse [ Browser newOnClass: self methodClass selector: self selector ] +{ #category : #'as yet unclassified' } +DCSmalltalkMethod >> category [ + + ^ self methodClass organization categoryOfElement: self selector +] + { #category : #'as yet unclassified' } DCSmalltalkMethod >> emptyIn: aClass [ @@ -60,6 +72,13 @@ DCSmalltalkMethod >> ignoreChangeNotifierDuring: aBlock [ aBlock ensure: [self removeProperty: #ignoreChangeNotifier] ] +{ #category : #'as yet unclassified' } +DCSmalltalkMethod >> in: aClass with: aString [ + + methodClass := aClass. + self addMorphBack: (DCBlock parseBlock: aString language: SBTSSmalltalk) +] + { #category : #'as yet unclassified' } DCSmalltalkMethod >> initialize [ @@ -134,6 +153,12 @@ DCSmalltalkMethod >> object [ ifAbsent: [self] ] +{ #category : #'as yet unclassified' } +DCSmalltalkMethod >> relatedClass [ + + ^ self methodClass +] + { #category : #'as yet unclassified' } DCSmalltalkMethod >> reloadMethodInPlaceUndoable: aBoolean [ @@ -159,7 +184,10 @@ DCSmalltalkMethod >> saveTryFixing: aFixBoolean quick: aQuickBoolean [ ifTrue: [self externalModification: false] ifFalse: [^ false]. - self firstSubmorph queryState errorsDo: [:error | aFixBoolean ifTrue: [error tryFixIfFail: [^ false]] ifFalse: [^ false]]. + self firstSubmorph queryState errorsDo: [:error | + aFixBoolean ifTrue: [error tryFixIfFail: [^ false]] ifFalse: [^ false]. + "errors are applied and removed on the next tick only" + self world ifNotNil: #doOneCycleNow]. text := aQuickBoolean ifTrue: [self method sourceString] ifFalse: [ [ @@ -173,7 +201,7 @@ DCSmalltalkMethod >> saveTryFixing: aFixBoolean quick: aQuickBoolean [ self ignoreChangeNotifierDuring: [ newSelector := self methodClass compile: text withSqueakLineEndings asText - classified: nil]. + classified: self category]. ^ true ] @@ -182,7 +210,7 @@ DCSmalltalkMethod >> saveTryFixing: aFixBoolean quick: aQuickBoolean [ DCSmalltalkMethod >> selector [ | selector | - selector := (self method queryAll: '[(unary_selector) (binary_selector) (keyword_selector)] @') first. + selector := (self method queryAll: '[(unary_selector) (binary_selector) (keyword_selector)] @') ifEmpty: [^ ''] ifNotEmpty: #first. ^ (((selector childSandblocks viewFrom: 1 by: 2) collect: [:p | p contents]) joinSeparatedBy: '') asSymbol ] diff --git a/packages/DomainCode-Parser/DCText.class.st b/packages/DomainCode-Parser/DCText.class.st index f35bd96..3619a45 100644 --- a/packages/DomainCode-Parser/DCText.class.st +++ b/packages/DomainCode-Parser/DCText.class.st @@ -104,6 +104,12 @@ DCText >> initialize [ range := SBTSRange null ] +{ #category : #'as yet unclassified' } +DCText >> isExpression [ + + ^ false +] + { #category : #'as yet unclassified' } DCText >> isReplacement [ diff --git a/packages/Sandblocks-TSSmalltalk/SBTSSmalltalk.class.st b/packages/Sandblocks-TSSmalltalk/SBTSSmalltalk.class.st index 590f5f6..b04c486 100644 --- a/packages/Sandblocks-TSSmalltalk/SBTSSmalltalk.class.st +++ b/packages/Sandblocks-TSSmalltalk/SBTSSmalltalk.class.st @@ -1305,10 +1305,7 @@ SBTSSmalltalk class >> spaceBetween: aBlock and: anotherBlock lastCharacterOfFir (aBlock type = 'string' and: [anotherBlock type = 'string']) ifTrue: [^ true]. anotherBlock type = #'block_argument' ifTrue: [^ true]. aCharacter = $: ifTrue: [^ true]. - (anotherBlock type = 'ERROR' and: [ - anotherBlock firstDeepChild contents - ifNotEmpty: [:e | e first isLetter not] - ifEmpty: [false]]) ifTrue: [^ false]. + "( anotherBlock type ='ERROR' and: [ anotherBlock firstDeepChild contents ifNotEmpty: [ :e| e first isLetter not ]ifEmpty: [ false ] ] )ifTrue: [ ^false ]" (aBlock contents = ':=' or: [anotherBlock contents = ':=']) ifTrue: [^ true]. anotherBlock alias = #'unary_identifier' ifTrue: [^ true]. ^ super spaceBetween: aBlock and: anotherBlock lastCharacterOfFirst: aCharacter diff --git a/packages/Sandblocks-TreeSitter/SBTSGrammar.class.st b/packages/Sandblocks-TreeSitter/SBTSGrammar.class.st index a422488..1f5bdd7 100644 --- a/packages/Sandblocks-TreeSitter/SBTSGrammar.class.st +++ b/packages/Sandblocks-TreeSitter/SBTSGrammar.class.st @@ -357,6 +357,14 @@ SBTSGrammar >> inlinedRules [ ^ inlinedRules ] +{ #category : #testing } +SBTSGrammar >> is: aSymbol subtypeOf: anotherSymbol [ + + self assert: (self isSuperType: anotherSymbol). + aSymbol = anotherSymbol ifTrue: [^ true]. + ^ (self bodyNodeForRule: anotherSymbol) allSubTypesRecursive anySatisfy: [:node | node type = aSymbol] +] + { #category : #testing } SBTSGrammar >> isBlockInlined: aSymbol [ diff --git a/packages/Sandblocks-TreeSitter/SBTSLanguage.class.st b/packages/Sandblocks-TreeSitter/SBTSLanguage.class.st index 113550f..498a5fa 100644 --- a/packages/Sandblocks-TreeSitter/SBTSLanguage.class.st +++ b/packages/Sandblocks-TreeSitter/SBTSLanguage.class.st @@ -146,6 +146,12 @@ SBTSLanguage class >> emptyLineSymbol [ ^ #empty ] +{ #category : #configuration } +SBTSLanguage class >> expressionTypes [ + + ^ #(#expression) +] + { #category : #'loading - helper' } SBTSLanguage class >> fetchLibrary: aGithubString branch: aString [ diff --git a/packages/Sandblocks-TreeSitter/SBTSNodeBase.class.st b/packages/Sandblocks-TreeSitter/SBTSNodeBase.class.st index cd30691..a18ac49 100644 --- a/packages/Sandblocks-TreeSitter/SBTSNodeBase.class.st +++ b/packages/Sandblocks-TreeSitter/SBTSNodeBase.class.st @@ -57,6 +57,19 @@ SBTSNodeBase >> allSubTypesDo: aBlock parents: aCollection [ ] +{ #category : #hierarchy } +SBTSNodeBase >> allSubTypesRecursive [ + + | found pending | + found := OrderedCollection new. + pending := OrderedCollection with: self. + [pending notEmpty] whileTrue: [ + pending removeFirst allSubTypes do: [:type | + found add: type. + pending add: type]]. + ^ found +] + { #category : #hierarchy } SBTSNodeBase >> allSuperTypesDo: aBlock [