--- a/DoWhatIMeanSupport.st Sun May 01 09:46:40 2016 +0200
+++ b/DoWhatIMeanSupport.st Sun May 01 18:19:51 2016 +0200
@@ -1078,7 +1078,10 @@
!
isKey:k1 nextTo:k2 onKeyboard:keys
- "return true, if k1 and k2 are adjacent keys on the keyboard defined by keys"
+ "return true, if k1 and k2 are adjacent keys on the keyboard defined by keys.
+ This is used to specially priorize plausible typing errors of adjacent keys.
+ (typo checker uses a modified levenshtein,
+ in which keys next to each other are valued differently)"
|row1 row2 col1 col2|
@@ -1093,6 +1096,8 @@
self isKey:$a nextTo:$q
self isKey:$a nextTo:$x
"
+
+ "Modified (comment): / 01-05-2016 / 12:19:24 / cg"
!
keyboard
@@ -1833,11 +1838,30 @@
aNode isMessage ifTrue:[
nodeSelector := aNode selector.
+
+ "/ heuristic: quickly assume boolean for some:
+ (
+ #(
+ isNil notNil isEmptyOrNil notEmptyOrNil
+ > >= < <= = == ~ ~=
+ isInteger isNumber isArray
+ knownAsSymbol
+ ) includes:nodeSelector
+ ) ifTrue:[
+ ^ { True } "/ use True, because boolean does not include the full protocol
+ ].
+
nodeReceiver := aNode receiver.
"/ some hardwired knowlegde here
receiverClass := self classOfNode:nodeReceiver.
receiverClass notNil ifTrue:[
+ nodeSelector == #theNonMetaclass ifTrue:[
+ ^ { receiverClass theNonMetaclass class }
+ ].
+ nodeSelector == #theMetaclass ifTrue:[
+ ^ { receiverClass theMetaclass class }
+ ].
nodeSelector == #class ifTrue:[
^ { receiverClass class }
].
@@ -1863,28 +1887,24 @@
]
].
].
- classOrNil notNil ifTrue:[
- (nodeReceiver isSelf and:[nodeSelector = #'class']) ifTrue:[
- ^ { classOrNil class }
+
+ #(
+ asFilename Filename
+ asOrderedCollection OrderedCollection
+ asArray Array
+ asSet Set
+ size SmallInteger
+ hash SmallInteger
+ identityHash SmallInteger
+ class Class
+ theMetaclass Metaclass
+ theNonMetaclass Class
+ ) pairWiseDo:[:sel :clsName |
+ nodeSelector == sel ifTrue:[
+ ^ { Smalltalk at:clsName }
].
].
- (nodeSelector = #'asFilename') ifTrue:[
- ^ { Filename }
- ].
- (nodeSelector = #'asOrderedCollection') ifTrue:[
- ^ { OrderedCollection }
- ].
- (nodeSelector = #'asArray') ifTrue:[
- ^ { Array }
- ].
- (nodeSelector = #'asSet') ifTrue:[
- ^ { Set }
- ].
- (nodeSelector = #'size') ifTrue:[
- ^ { SmallInteger }
- ].
-
"/ some wellknown boolean returners (need better type inference here)
(#( isNil notNil not isEmptyOrNil notEmptyOrNil notEmpty isEmpty
isBehavior isMeta
@@ -1900,13 +1920,10 @@
"/ assume numeric
^ { Number }
].
-
- ( #( class theMetaclass theNonMetaclass ) includes:nodeSelector) ifTrue:[
- "/ assume behavior
- ^ { Behavior }
- ].
].
^ nil
+
+ "Modified: / 01-05-2016 / 12:33:20 / cg"
!
codeCompletionForLiteralSymbol:nodeOrNil element:tokenOrNil considerAll:considerAll into:actionBlock
@@ -2044,21 +2061,21 @@
receiverNodeClassIfKnown
offerParenthisationAroundNode parenthesisAroundIndex
parentNodeToParenthesize|
-
+
"/ Transcript show:'node '; show:node; show:' ; '.
"/ Transcript show:'msg in '; show:methodOrNil; show:' / '; showCR:classOrNil.
-
+
offerParenthisationAroundNode := nil.
-
+
"/ node at:1
-
+
findBest :=
[:node :selector |
|srchClasses bestSelectors bestPrefixes
allMessagesSentToVariable classesImplementingAllMessages|
-
+
srchClasses := self classesOfNode:node.
-
+
srchClasses isEmptyOrNil ifTrue:[
node isVariable ifTrue:[
allMessagesSentToVariable := Set new.
@@ -2092,7 +2109,7 @@
] ifFalse:[
srchClasses do:[:srchClass |
|bestForThisClass|
-
+
bestForThisClass := Parser findBest:50 selectorsFor:selector in:srchClass forCompletion:true.
bestForThisClass := self
withoutSelectorsUnlikelyFor:srchClass
@@ -2101,36 +2118,36 @@
bestSelectors addAll:bestForThisClass.
].
].
- (bestSelectors includes:selector) ifTrue:[
- bestSelectors := bestSelectors select:[:sel | sel size > selector size].
- ].
+ "/ remove the already typed-in selector itself, in case.
+ bestSelectors remove:selector ifAbsent:[].
bestSelectors := bestSelectors asOrderedCollection.
bestSelectors
].
-
+
selector := node selector.
lcSelector := selector asLowercase.
parentNode := node parent.
nodeReceiver := node receiver.
-
+
"/ if there is already space before the cursor, and the parent node is not a message,
"/ do not attempt to complete the current message.
"/ If it is a message, we will look for parent-message completion also below (best2 stuff)
(codeView characterBeforeCursor ? $ ) isSeparator ifTrue:[
selector isKeyword ifFalse:[
- (parentNode notNil and:[ parentNode isMessage ]) ifFalse:[
- ^ self.
- ].
+ ^ self
+"/ (parentNode notNil and:[ parentNode isMessage ]) ifFalse:[
+"/ ^ self.
+"/ ].
].
].
-
+
"/ only do this if the node-message has no parents around
node parentheses isEmptyOrNil ifTrue:[
bestSelectors := findBest value:nodeReceiver value:selector.
] ifFalse:[
bestSelectors := OrderedCollection new.
].
-
+
"/ if the receiver is a real variable,
"/ we can look for other messages being sent to that variable in the current method.
"/ Also, if there are assignment to it (like constants or '<class> new'), use that as a hint...
@@ -2139,74 +2156,82 @@
and:[ nodeReceiver isSelf not
and:[ nodeReceiver isSuper not ]]])
ifTrue:[
- |classesFromAssignmentsToReceiver otherMessagesToReceiver possibleClasses possibleClassesFromOtherSends|
-
- classesFromAssignmentsToReceiver :=
- tree allAssignmentNodes
- collect:[:eachAssignmentNode |
- |cls|
- (nodeReceiver = eachAssignmentNode variable
- and:[ (cls := self classOfNode:eachAssignmentNode value) notNil ]
- ) ifTrue:[ cls ] ifFalse:[ nil ]
- ]
- thenSelect:[:classOrNil | classOrNil notNil].
-
- possibleClasses := classesFromAssignmentsToReceiver.
-
- otherMessagesToReceiver := Set new.
- tree allMessageNodesDo:[:eachMessageNode |
- (nodeReceiver = eachMessageNode receiver
- and:[ selector ~= eachMessageNode selector]
- ) ifTrue:[
- otherMessagesToReceiver add:eachMessageNode selector
+ |receiverName classesFromAssignmentsToReceiver otherMessagesToReceiver possibleClasses possibleClassesFromOtherSends|
+
+ receiverName := nodeReceiver name.
+
+ classesFromAssignmentsToReceiver := Set new.
+ "/ assignments...
+ tree allAssignmentNodesDo:[:eachAssignmentNode |
+ |exprCls leftSide|
+
+ leftSide := eachAssignmentNode variable.
+ leftSide name = receiverName ifTrue:[
+ exprCls := self classOfNode:eachAssignmentNode value.
+ exprCls notNil ifTrue:[
+ classesFromAssignmentsToReceiver add:exprCls
+ ]
]
].
- otherMessagesToReceiver notEmpty ifTrue:[
- possibleClassesFromOtherSends :=
- Smalltalk
- allClassesForWhich:[:cls |
- cls isLoaded
- and:[ otherMessagesToReceiver
- conform:[:eachSelectorSent | cls includesSelector: "canUnderstand:" eachSelectorSent]]
- ].
- possibleClasses := possibleClasses , possibleClassesFromOtherSends.
+ possibleClasses := classesFromAssignmentsToReceiver.
+
+ possibleClasses isEmpty ifTrue:[
+ "/ messages sent
+ otherMessagesToReceiver := Set new.
+ tree allMessageNodesDo:[:eachMessageNode |
+ (nodeReceiver = eachMessageNode receiver
+ and:[ selector ~= eachMessageNode selector]
+ ) ifTrue:[
+ otherMessagesToReceiver add:eachMessageNode selector
+ ]
+ ].
+ otherMessagesToReceiver notEmpty ifTrue:[
+ "/ classes which respond to all
+ possibleClassesFromOtherSends :=
+ Smalltalk
+ allClassesForWhich:[:cls |
+ cls isLoaded
+ and:[ otherMessagesToReceiver
+ conform:[:eachSelectorSent | cls canUnderstand:eachSelectorSent]]
+ ].
+ possibleClasses := possibleClasses , possibleClassesFromOtherSends.
+ ].
].
-
+
"/ if the receiver is a classVar/classInstVar,
"/ include the class of its current value and UndefinedObject.
"/ This helps to complete class methods and (lazy) initializer code.
(classOrNil notNil) ifTrue:[
|tryValue currentValue|
-
+
tryValue := false.
- (classOrNil theNonMetaclass allClassVarNames includes: nodeReceiver name) ifTrue:[
+ (classOrNil theNonMetaclass allClassVarNames includes: receiverName) ifTrue:[
tryValue := true.
- currentValue := classOrNil theNonMetaclass classVarAt:nodeReceiver name.
+ currentValue := classOrNil theNonMetaclass classVarAt:receiverName.
] ifFalse:[
- (classOrNil isMeta and:[ classOrNil allInstVarNames includes: nodeReceiver name ]) ifTrue:[
+ (classOrNil isMeta and:[ classOrNil allInstVarNames includes: receiverName ]) ifTrue:[
tryValue := true.
- currentValue := classOrNil theNonMetaclass instVarNamed:nodeReceiver name.
+ currentValue := classOrNil theNonMetaclass instVarNamed:receiverName.
].
].
tryValue ifTrue:[
- currentValue notNil ifTrue:[ possibleClasses := { UndefinedObject } , possibleClasses ].
possibleClasses := { currentValue class } , possibleClasses.
].
].
-
+
(possibleClasses notEmpty and:[possibleClasses size < 15]) ifTrue:[
bestSelectors :=
(possibleClasses
collectAll:[:eachClass |
Parser findBest:30 selectorsFor:selector in:eachClass forCompletion:true.
] as:Set) asOrderedCollection.
-
+
"/ if any of those is a prefix-keyword of the selector,
"/ do not offer it (i.e. ifTrue:ifFalse: is already present, don't offer ifTrue:ifFalse: again.
bestSelectors := bestSelectors reject: [:sel | (selector startsWith: sel) or: [selector endsWith: sel]].
].
].
-
+
"/ if we are behind a keyword messages colon,
"/ only look for matching prefix selectors;
"/ also, a good completion is to insert an argument;
@@ -2214,21 +2239,22 @@
"/ Array new:1
selector isKeyword ifTrue:[
(node arguments size = selector numArgs) ifTrue:[
- offerParenthisationAroundNode := node
+ offerParenthisationAroundNode := node.
+Transcript show:'2:'; showCR:node.
].
-
+
codeView characterBeforeCursor == $: ifTrue:[
(bestSelectors select:[:sel | sel asLowercase startsWith:lcSelector]) isEmpty ifTrue:[
"/ nothing better around
|argIndex argNames impls|
-
+
argIndex := node selectorParts size.
argNames := Set new.
impls := Smalltalk allImplementorsOf:selector.
impls size < 10 ifTrue:[
impls do:[:eachImplClass |
|mthd argName|
-
+
mthd := (eachImplClass compiledMethodAt:selector).
argName := (mthd methodArgNames ? #()) at:argIndex ifAbsent:nil.
argName notNil ifTrue:[
@@ -2250,9 +2276,10 @@
bestSelectors := bestSelectors select:[:sel | sel isUnarySelector ]
]
].
-
- bestSelectors := bestSelectors asOrderedCollection sort:[:a :b | a size < b size].
-
+
+"/ bestSelectors := bestSelectors asOrderedCollection.
+"/ bestSelectors sort:[:a :b | a size < b size].
+
(selector isUnarySelector and:[ parentNode notNil and:[ parentNode isMessage ]]) ifTrue:[
(selector2 := parentNode selector) isKeywordSelector ifTrue:[
"/ if its a unary message AND the parent is a keyword node, look for parent completion too.
@@ -2262,14 +2289,15 @@
bestSelectors2 := bestSelectors2 select:[:sel | sel isKeywordSelector and:[ sel startsWith:selector2]].
bestSelectors2 := bestSelectors2 asOrderedCollection sort:[:a :b | a size < b size].
bestSelectors := bestSelectors reject:[:sel | bestSelectors2 includes:sel].
-
+
"/ if the parent has a valid selector, offer parenthization
(Smalltalk someImplementorOf:selector2) notNil ifTrue:[
offerParenthisationAroundNode := parentNode.
+ "/ Transcript show:'2:'; showCR:parentNode.
].
] ifFalse:[
|kwSels|
-
+
"/ if its a unary message AND the parent is a unary or binary node, try again, sending the partial message
"/ as a keyword to the parent node.
"/ this is the case when after "foo binOp bar if", which should include ifTrue: in the result.
@@ -2293,14 +2321,14 @@
"/ /
"/ /
"/ arg
-
+
kwSels := findBest value:parentNode value:selector.
kwSels := kwSels select:[:sel | sel isKeywordSelector].
-
+
kwSels := kwSels asOrderedCollection sort:[:a :b | a size < b size].
-
+
bestSelectors := bestSelectors reject:[:sel | kwSels includes:sel].
-
+
"/ these need to go to bestSelectors (see editAction)
parentNodeClassIfKnown := self classOfNode:parentNode.
(parentNodeClassIfKnown notNil and:[ parentNodeClassIfKnown includesBehavior: Boolean ]) ifTrue:[
@@ -2315,7 +2343,7 @@
withoutSelectorsUnlikelyFor:parentNodeClassIfKnown
from:kwSels
forPartial:selector.
-
+
"/ put keyword selectors in front, because they are very likely
bestSelectors := kwSels , bestSelectors.
] ifFalse:[
@@ -2324,7 +2352,7 @@
].
]
].
-
+
(selector isUnarySelector and:[ node isMessage ]) ifTrue:[
receiverNodeClassIfKnown := self classOfNode:nodeReceiver.
(receiverNodeClassIfKnown notNil and:[ receiverNodeClassIfKnown includesBehavior: Boolean ]) ifTrue:[
@@ -2337,10 +2365,11 @@
forPartial:selector.
].
].
- (selector isUnarySelector
- and:[ parentNode notNil
- and:[ parentNode isMessage
- and:[ (parentNode selector isUnarySelector not) ]]]) ifTrue:[
+ (parentNode notNil
+ and:[ parentNode isMessage
+ and:[ ((parentNode selector isUnarySelector not) and:[selector isUnarySelector])
+ or:[ ((parentNode selector isKeywordSelector) and:[selector isBinarySelector]) ]]]
+ ) ifTrue:[
"/ completing an already existing keyword or binary message with something starting with
"/ if, and, or or while.
"/ Here, offer a special completion which inserts parenthesis / brackets around the already
@@ -2348,15 +2377,19 @@
"/ expr wh
"/ ->
"/ [expr] whileXX:[]
- ((
+ true "((
#( 'ifTrue' 'ifFalse' 'and' 'or' 'do' 'keysAndValuesDo' 'whileTrue' 'whileFalse' 'ensure' 'on')
- ) contains:[:part | part startsWith:selector]) ifTrue:[
+ ) contains:[:part | part startsWith:selector])" ifTrue:[
(Smalltalk someImplementorOf:parentNode selector) notNil ifTrue:[
|selsP selsB|
-
+
selsP := #( 'ifTrue:' 'ifFalse:' 'and' 'or' 'do' 'keysAndValuesDo' )
select:[:sel | sel startsWith:selector]
thenCollect:[:sel | '(',parentNode selector,') ',sel].
+ ( #( 'whileTrue:' 'whileFalse:' 'ensure:' 'on:do:' ) contains:[:sel | sel startsWith:selector])
+ ifFalse:[
+ selsP := selsP copyWith:'(',parentNode selector,') ',selector
+ ].
selsB := #( 'whileTrue:' 'whileFalse:' 'ensure:' 'on:do:' )
select:[:sel | sel startsWith:selector]
thenCollect:[:sel | '[',parentNode selector,'] ',sel].
@@ -2374,7 +2407,7 @@
) contains:[:part | part startsWith:selector]) ifTrue:[
(node receiver isBlock) ifFalse:[
|sels|
-
+
(node receiver isMessage not
or:[ (Smalltalk someImplementorOf:node receiver selector) notNil ]) ifTrue:[
sels := #( 'whileTrue:' 'whileFalse:' 'ensure:' 'on:do:' )
@@ -2386,7 +2419,7 @@
].
].
].
-
+
allBest := (bestSelectors ? #()) , (bestSelectors2 ? #()).
allBest sort:
[:a :b |
@@ -2419,7 +2452,7 @@
split :=
[:list :splitHow |
|part1 part2 all|
-
+
part1 := list select:splitHow.
part2 := list reject:splitHow.
part1 isEmpty ifTrue:[
@@ -2442,12 +2475,12 @@
(sel asLowercase startsWith:lcSelector)
or:[sel startsWith:selector2]].
].
-
+
"/ if receiver is super, always include the method's own selector
nodeReceiver isSuper ifTrue:[
(tree isMethod) ifTrue:[
|mSel|
-
+
mSel := tree selector.
mSel notNil ifTrue:[
(mSel startsWith:selector) ifTrue:[
@@ -2460,23 +2493,36 @@
]
]
].
-
- allBest := (bestWithParenthesis ? #()) , allBest.
- allBest isEmptyOrNil ifTrue:[
+
+ (allBest isEmptyOrNil and:[bestWithParenthesis isEmptyOrNil]) ifTrue:[
^ self
].
-
- "/ the one's which are a prefix are moved towards the top of the list
- allBest := split
- value:allBest
- value:[:sel | sel notNil and:[sel asLowercase startsWith:lcSelector]].
-
- rememberedNodes notNil ifTrue:[
- selectorsSentInCode :=
- (rememberedNodes
- select:[:node | node isMessage]
- thenCollect:[:node | node selector]) asSet.
- selectorsSentInCode remove:selector ifAbsent:[].
+
+ "/ see what is aready sent to this variable inside the code
+ nodeReceiver notNil ifTrue:[
+ nodeReceiver isVariable ifTrue:[
+ rememberedNodes notNil ifTrue:[
+ selectorsSentInCode :=
+ (rememberedNodes
+ select:[:node |
+ node isMessage
+ and:[node receiver isVariable
+ and:[node receiver name = nodeReceiver name]]]
+ thenCollect:[:node |
+ node selector]
+ ) asSet.
+ ] ifFalse:[
+ selectorsSentInCode := Set new.
+ tree allMessageNodesDo:[:msg |
+ (msg receiver isVariable
+ and:[msg receiver name = nodeReceiver name]
+ ) ifTrue:[
+ selectorsSentInCode add:msg selector
+ ].
+ ].
+ selectorsSentInCode remove:selector ifAbsent:[].
+ ].
+ ].
].
nodeReceiver notNil ifTrue:[
|classOrNil|
@@ -2490,13 +2536,17 @@
]
].
selectorsImplementedInClass notNil ifTrue:[
- "/ the one's already sent in the code are moved to the top of the list.
+ "/ the one's implemented in the class itself are moved to the top of the list.
allBest := split value:allBest value:[:sel | selectorsImplementedInClass includes:sel].
].
selectorsSentInCode notNil ifTrue:[
"/ the one's already sent in the code are moved to the top of the list.
+ "/ trouble is: parser bails out on error, so most of the time, we only see
+ "/ selectors sent previously. sigh.
allBest := split value:allBest value:[:sel | selectorsSentInCode includes:sel].
].
+
+"/ this makes it very slow
"/false ifTrue:[
"/ srchClass notNil ifTrue:[
"/ implClass := srchClass whichClassIncludesSelector:best.
@@ -2515,35 +2565,58 @@
"/ ].
"/ self information:info.
"/].
-
+
+ "/ the one's which are a prefix are moved towards the top of the list
+ allBest := split
+ value:allBest
+ value:[:sel | sel notNil and:[sel asLowercase startsWith:lcSelector]].
+
+ "/ heuristic hack:
+ "/ 'i' and 'w' generate lists in which ifXXX / whileXXX are not at the top of the list.
+ "/ we know, that those are most often wanted!!
+ selector size <= 2 ifTrue:[
+ allBest := split
+ value:allBest
+ value:[:sel |
+ #(ifTrue: ifFalse: isNil notNil whileTrue whileFalse) includes:sel
+ ].
+ ].
+
+ self sortUsefulSelectorsIn:allBest. "/cosmetics
+
+ "/ parenthesizers always at the end.
+ bestWithParenthesis notEmptyOrNil ifTrue:[
+ allBest := allBest , bestWithParenthesis.
+ ].
+
"/ self at:1 put:#foo
"/ Array new:10
offerParenthisationAroundNode notNil ifTrue:[
allBest := allBest copyWith:( '(',selector,')' ).
parenthesisAroundIndex := allBest size.
].
-
+
editAction :=
[:index |
- |crsrPos chosen parentsToInsert action|
-
+ |crsrPos chosen parenthesisToInsert action|
+
action := nil.
crsrPos := codeView characterPositionOfCursor.
chosen := allBest at:index.
-
+
chosen ~= selector ifTrue:[
(bestWithParenthesis notNil and:[bestWithParenthesis includes:chosen]) ifTrue:[
"/ for input like:
"/ chosen at: 10 if
"/ put parenthesis around, and add ifTrue/ifFalse
"/ i.e.: (chosen at:10) ifTrue:[]
-
+
"/ for input like:
"/ a > 10 wh
"/ put brackets around and add whileTrue/whileFalse
"/ i.e.: [a > 10] whileTrue:[]
- parentsToInsert := chosen first == $( ifTrue:'()' ifFalse:'[]'.
- chosen := (chosen copyFrom:(chosen lastIndexOf:parentsToInsert second)+1) withoutSeparators.
+ parenthesisToInsert := chosen first == $( ifTrue:'()' ifFalse:'[]'.
+ chosen := (chosen copyFrom:(chosen lastIndexOf:parenthesisToInsert second)+1) withoutSeparators.
] ifFalse:[
(offerParenthisationAroundNode notNil and:[index = parenthesisAroundIndex]) ifTrue:[
"/ for input like:
@@ -2558,7 +2631,7 @@
].
]
].
-
+
action isNil ifTrue:[
numArgs := chosen numArgs.
(bestSelectors2 notEmptyOrNil and:[bestSelectors2 includes:chosen]) ifTrue:[
@@ -2567,50 +2640,50 @@
selectorParts := node selectorParts.
].
nSelParts := selectorParts size.
-
+
newParts := chosen asCollectionOfSubstringsSeparatedBy:$:.
newParts := newParts select:[:part | part size > 0].
-
+
action :=
[
|positionOfFirstArg newCursorPosition stop checkForArgumentTemplates
newPart oldPartialToken start|
-
+
checkForArgumentTemplates := (selector isUnarySelector and:[chosen isKeywordSelector]).
numArgs > nSelParts ifTrue:[
"/ new selector has more arguments; append them
stop := selectorParts last stop.
codeView deleteFromCharacterPosition:stop+1 to:crsrPos-1.
-
+
"/ append the rest ...
(numArgs min:newParts size) downTo:(nSelParts+1) do:[:idx |
|newPart|
-
+
newPart := newParts at:idx.
newPart := newPart , ':'.
-
+
(codeView characterAtCharacterPosition:stop) == $: ifFalse:[
newPart := ':' , newPart.
].
newPart := (codeView characterAtCharacterPosition:stop) asString , newPart.
-
+
codeView replaceFromCharacterPosition:stop to:stop with:newPart.
"/ remember the leftMost replacement's end as new cursor position
newCursorPosition := stop + newPart size
].
checkForArgumentTemplates := true.
].
-
+
"/ replace existing parts
(nSelParts min:newParts size) downTo:1 do:[:idx |
|skipColon|
-
+
skipColon := 0.
newPart := newParts at:idx.
oldPartialToken := selectorParts at:idx.
start := oldPartialToken start.
stop := oldPartialToken stop.
-
+
(chosen endsWith:$:) ifTrue:[
(codeView characterAtCharacterPosition:stop+1) == $: ifFalse:[
newPart := newPart , ':'.
@@ -2622,7 +2695,7 @@
newPart := newPart , ':'
] ifFalse:[
|nextChar|
-
+
nextChar := codeView characterAtCharacterPosition:stop+1.
nextChar isSeparator ifFalse:[
nextChar == $. ifFalse:[
@@ -2634,13 +2707,13 @@
"/ ] ifFalse:[
"/ codeView replaceFromCharacterPosition:start to:stop with:newPart.
].
-
+
oldPartialToken value ~= newPart ifTrue:[
codeView replaceFromCharacterPosition:start to:stop with:newPart.
-
+
oldLen := stop - start + 1.
newLen := newPart size.
-
+
"/ codeView selectFromCharacterPosition:start+oldLen to:start+newLen-1.
"/ remember the leftMost replacement's end as new cursor position
newCursorPosition := start + newPart size + skipColon. "/ (newLen-oldLen) + 1.
@@ -2652,45 +2725,108 @@
codeView cursorRight. "/ avoid going to the next line !!
].
codeView dontReplaceSelectionOnInput.
-
+
checkForArgumentTemplates ifTrue:[
"/ add opening brackets, etc.
self insertAdditonalStuffAfterSelector:chosen.
].
- parentsToInsert notNil ifTrue:[
+ parenthesisToInsert notNil ifTrue:[
|sav pos|
-
+
sav := codeView characterPositionOfCursor-1.
"/ check if already parenthized
- node receiver hasParentheses ifTrue:[
- pos := node receiver parentheses first first.
+ parentNodeToParenthesize hasParentheses ifTrue:[
+ pos := parentNodeToParenthesize parentheses first first.
codeView selectFromCharacterPosition:pos to:pos.
- codeView replaceSelectionBy:(parentsToInsert copyFirst:1) asString.
-
- pos := node receiver parentheses first last.
+ codeView replaceSelectionBy:(parenthesisToInsert copyFirst:1) asString.
+
+ pos := parentNodeToParenthesize parentheses first last.
codeView selectFromCharacterPosition:pos to:pos.
- codeView replaceSelectionBy:(parentsToInsert copyLast:1) asString.
+ codeView replaceSelectionBy:(parenthesisToInsert copyLast:1) asString.
codeView cursorToCharacterPosition:sav; cursorRight
] ifFalse:[
- codeView insertString:(parentsToInsert copyLast:1) atCharacterPosition:node receiver stop+1.
- codeView insertString:(parentsToInsert copyFirst:1) atCharacterPosition:node receiver "parentNode" start.
+ codeView insertString:(parenthesisToInsert copyLast:1) atCharacterPosition:node receiver stop+1.
+ codeView insertString:(parenthesisToInsert copyFirst:1) atCharacterPosition:parentNodeToParenthesize start.
codeView cursorToCharacterPosition:sav+2; cursorRight
].
].
].
].
-
+
codeView
undoableDo:action
info:'Completion'.
].
].
-
+
actionBlock value:allBest value:editAction value:nil.
"Created: / 10-11-2006 / 13:18:27 / cg"
"Modified: / 16-02-2010 / 10:33:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
- "Modified: / 01-05-2016 / 09:46:27 / cg"
+ "Modified: / 01-05-2016 / 17:48:54 / cg"
+!
+
+codeCompletionForMessageTo:node into:actionBlock
+ "find good suggestions for a message send to node, with no input yet"
+
+ |knownClass suggestions selectorsImplementedInClass mostUseful|
+
+ (knownClass := self classOfNode:node) isNil ifTrue:[^ self].
+
+ selectorsImplementedInClass := Set new.
+
+ knownClass withAllSuperclassesDo:[:cls |
+ cls ~~ Object ifTrue:[
+ selectorsImplementedInClass addAll:cls selectors.
+ ]
+ ].
+
+ knownClass isMeta ifTrue:[
+ selectorsImplementedInClass :=
+ selectorsImplementedInClass reject:[:sel |
+ |mthd|
+
+ mthd := knownClass lookupMethodFor:sel.
+ mthd notNil and:[mthd category startsWith: 'documentation']
+ ].
+
+ knownClass theNonMetaclass isAbstract ifTrue:[
+ mostUseful := selectorsImplementedInClass select:[:sel |
+ knownClass implements:sel
+ ]
+ ] ifFalse:[
+ mostUseful := selectorsImplementedInClass select:[:sel |
+ |mthd|
+
+ mthd := knownClass lookupMethodFor:sel.
+ mthd notNil and:[mthd category startsWith: 'instance']
+ ].
+ ]
+ ] ifFalse:[
+ mostUseful :=
+ #(
+ "/ blocks
+ ifTrue: ifFalse: whileTrue: whileFalse: on:do: ensure:
+ whileTrue whileFalse loop
+ "/ any
+ isNil notNil isEmpty notEmpty
+ ).
+ ].
+
+ mostUseful notNil ifTrue:[
+ suggestions :=
+ (selectorsImplementedInClass select:[:sel | mostUseful includes:sel]) asNewOrderedCollection sort
+ ,
+ (selectorsImplementedInClass reject:[:sel | mostUseful includes:sel]) asNewOrderedCollection sort.
+ ] ifFalse:[
+ suggestions := selectorsImplementedInClass asNewOrderedCollection sort.
+ ].
+
+ self sortUsefulSelectorsIn:suggestions. "/cosmetics
+ actionBlock value:suggestions value:nil value:nil.
+
+ "Created: / 01-05-2016 / 17:01:21 / cg"
+ "Modified: / 01-05-2016 / 18:13:50 / cg"
!
codeCompletionForMethodSpec:node
@@ -3037,20 +3173,25 @@
globalFactor localFactor selectorOfMessageToNode implementors argIdx namesUsed kwPart
editAction suggestions nameIsOK longerNames setOfNames otherArgNames
suggestionsWithInfo|
-
+
"/ Transcript show:'var in '; show:methodOrNil; show:' / '; showCR:classOrNil.
classOrNil notNil ifTrue:[
nonMetaClass := classOrNil theNonMetaclass.
].
-
+
nm := node name.
-
+
+ crsrPos := codeView characterPositionOfCursor.
+
"/ if we are behind the variable and a space has already been entered,
"/ the user is probably looking for a message selector.
"/ If the variable represents a global, present its instance creation messages
- crsrPos := codeView characterPositionOfCursor.
- char := codeView characterAtCharacterPosition:crsrPos-1.
- char isSeparator ifTrue:[
+ char := codeView characterBeforeCursor.
+ "/ char := codeView characterAtCharacterPosition:crsrPos-1. -wrong if beyond EOL
+ "/ Transcript show:'crsrPos: '; showCR:crsrPos.
+ "/ Transcript show:'varchar: '; showCR:char.
+
+ char == Character space ifTrue:[
nm knownAsSymbol ifTrue:[
classOrNil isNil ifTrue:[
nodeVal := Smalltalk at:nm asSymbol.
@@ -3077,7 +3218,7 @@
]
]
].
-
+
selectors := selectors1 order sort , #('-') , selectors2 order sort.
editAction :=
[:answer |
@@ -3090,15 +3231,12 @@
]
info:'completion'.
].
- actionBlock
- value:selectors
- value:editAction
- value:nil.
+ actionBlock value:selectors value:editAction value:nil.
^ self.
].
].
].
-
+
parent := node parent.
(parent notNil and:[parent isMessage]) ifTrue:[
node == parent receiver ifTrue:[
@@ -3148,9 +3286,10 @@
|distanceComputeBlock|
distanceComputeBlock := (getDistanceComputeBlockWithWeight value:factor).
- (eachNames includes:nm) ifTrue:[nameIsOK := true].
eachNames do:[:nameToAdd |
- (nameToAdd ~= nm) ifTrue:[ "/ not again
+ (nameToAdd = nm) ifTrue:[
+ nameIsOK := true
+ ] ifFalse:[ "/ not again
(variablesAlreadyAdded includes:nameToAdd) ifFalse:[ "/ not again
variablesAlreadyAdded add:nameToAdd.
allVariables add:nameToAdd.
@@ -3245,7 +3384,7 @@
addWithFactorBlock value:(codeView previousReplacements collect:[:p | p value asString]) value:(1.3 * localFactor).
] ifFalse:[
"/ locals in the block/method
- |names nameSpace|
+ |names nameSpace|
names := OrderedCollection withAll:node allVariablesOnScope.
setOfNames := Set withAll:names.
@@ -3267,6 +3406,7 @@
].
"/ (setOfNames includesAll:(eachScope allDefinedVariables)) ifFalse:[ self halt].
].
+
rememberedScopeNodes do:[:eachScope |
eachScope variableNodesDo:[:var |
(setOfNames includes:var name) ifFalse:[
@@ -3472,10 +3612,13 @@
longerNames size < 30 ifTrue:[
longerNames := allTheBest select:[:assoc | assoc key includesString:nm caseSensitive:false].
].
- longerNames notEmpty ifTrue:[
- allTheBest := longerNames.
+ longerNames isEmpty ifTrue:[
+ "/ no better name
+ ^ self
].
+ allTheBest := longerNames.
].
+
allTheBest size > 20 ifTrue:[
allTheBest := allTheBest copyTo:20.
"/ "/ remove all those which are below some threshold or are a prefix
@@ -3553,13 +3696,34 @@
suggestionsWithInfo :=
suggestions
collect:[:eachName |
- |val|
-
- val := self valueOfVariable:eachName.
- val isNil ifTrue:[
+ |val kind valAndKind printString|
+
+ valAndKind := self valueAndKindOfVariable:eachName.
+ valAndKind isNil ifTrue:[
eachName
] ifFalse:[
- eachName,' (',val class name,')'
+ val := valAndKind first.
+ kind := valAndKind second.
+
+ val isBehavior ifTrue:[
+ val isLoaded ifFalse:[
+ eachName,' ( ', ('autoloaded class in ',(val category ? 'unknown category')) allItalic,' )'
+ ] ifTrue:[
+ val isNameSpace ifTrue:[
+ eachName,' ( ', 'namespace' allItalic,' )'
+ ] ifFalse:[
+ eachName,' ( ', ('class in ',(val category ? 'unknown category')) allItalic,' )'
+ ]
+ ]
+ ] ifFalse:[
+ "/ Parser findBest:30 selectorsFor:'isLite' in:nil forCompletion:true
+
+ (val isLiteral and:[ (printString := val printString) size < 15 ]) ifTrue:[
+ eachName,' ( ',printString allItalic,' )'
+ ] ifFalse:[
+ eachName,' ( ',val classNameWithArticle allItalic,' )'
+ ].
+ ].
].
].
@@ -3567,7 +3731,7 @@
"Created: / 10-11-2006 / 13:16:33 / cg"
"Modified: / 16-02-2010 / 10:13:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
- "Modified: / 30-07-2013 / 08:36:11 / cg"
+ "Modified: / 01-05-2016 / 17:25:27 / cg"
!
findNodeForInterval:interval in:source
@@ -3639,15 +3803,31 @@
!
findNodeForInterval:interval in:source allowErrors:allowErrors mustBeMethod:mustBeMethod mustBeExpression:mustBeExpression
- "parse it as expression or method;
- if mustBeMethod is true, do not try a regular expressions (as in a workspace);
- if mustBeExpression is true, do not try method"
+ "parse source, and find the node which is in the given interval (typically a selection or a word in the source).
+
+ parse it as expression or method;
+ if mustBeMethod is true, do not try as expression;
+ if mustBeExpression is true, do not try as method
+ expression syntax parsing is done in workspaces (doIt).
+
+ Big hack as workaround a limitation of RBParser:
+ in case of an error, the parent chain of a node is usually not yet set.
+ (because the code is written as:
+ parentNode addChild:(self parseChild)
+ and the parent-chain of the parsed child is set in addChild).
+ But:
+ when doing code completion, having invalid syntax to parse is the normal case.
+ Workaround:
+ remember created nodes as the parse proceeds, and remember them.
+ Thus, I have the parent chain.
+ "
|intersectingNodes smallestIntersectingNode firstIntersectingNode
lastIntersectingNode onErrorBlock
nodeGenerationHook parserClass parser currentScopeNodes bestNode|
interval isEmpty ifTrue: [^ nil].
+
languageOrNil notNil ifTrue:[
parserClass := languageOrNil parserClass.
] ifFalse:[
@@ -3805,6 +3985,7 @@
"Created: / 16-09-2011 / 14:52:08 / cg"
"Modified: / 18-09-2013 / 16:47:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+ "Modified (comment): / 01-05-2016 / 10:05:10 / cg"
!
findNodeForInterval:interval inParseTree:parseTree
@@ -3960,6 +4141,33 @@
"Modified: / 28-08-2013 / 15:28:01 / cg"
!
+sortUsefulSelectorsIn:selectorList
+ "/ cosmetics:
+ "/ ifTrue / whileTrue should come before ifFalse/whileFalse
+ #(
+ ifTrue: ifFalse:
+ ifTrue:ifFalse: ifFalse:ifTrue:
+ whileTrue: whileFalse:
+ whileTrue whileFalse
+ whileTrue: whileTrue
+ whileFalse: whileFalse
+ new: basicNew:
+ new basicNew
+ ) pairWiseDo:[:sel1 :sel2 |
+ |idx1 idx2|
+
+ (idx1 := selectorList indexOf:sel1) ~~ 0 ifTrue:[
+ (idx2 := selectorList indexOf:sel2) ~~ 0 ifTrue:[
+ idx1 > idx2 ifTrue:[
+ selectorList swap:idx1 with:idx2
+ ]
+ ]
+ ].
+ ].
+
+ "Created: / 01-05-2016 / 17:48:02 / cg"
+!
+
treeForCode:source allowErrors:allowErrors
|tree|
@@ -4000,7 +4208,7 @@
legal, but stupid message send to be parsed...
(which happens often after inserting)"
- |node nodeParent checkedNode characterBeforeCursor nodeIsInTemporaries|
+ |node nodeParent checkedNode characterBeforeCursor|
"/ this is too naive and stupid; if there is a syntactic error,
"/ we will not find a node for a long time (stepping back more and more,
@@ -4011,7 +4219,7 @@
"/ that will also work for syntactic incorrect source code.
(mustBeExpression not and:[methodOrNil notNil or:[classOrNil notNil]]) ifTrue:[
node := self findNodeForInterval:interval in:source allowErrors:true mustBeMethod:true.
- ].
+ ].
node isNil ifTrue:[
node := self findNodeForInterval:interval in:source allowErrors:true mustBeMethod:false mustBeExpression:true.
node isNil ifTrue:[
@@ -4033,25 +4241,47 @@
characterBeforeCursor := source at:(characterPositionOfCursor-1 max:1). "/ codeView characterBeforeCursor.
characterBeforeCursor isNil ifTrue:[ "at begin of line" ^ self].
characterBeforeCursor == $. ifTrue:[ "at end of statement" ^ self].
-
+
node isVariable ifTrue:[
- |classes cls|
-
+ |nodeIsInTemporaries nodeIsInBlockArguments nodeIsInMethodArguments |
+
nodeIsInTemporaries :=
nodeParent notNil
and:[ nodeParent isSequence
and:[ nodeParent temporaries notEmptyOrNil
- and:[ node stop <= nodeParent temporaries last stop ]]].
-
- nodeIsInTemporaries ifFalse:[
- "/ cursor must be right after the variable
- codeView characterPositionOfCursor = (node stop + 1) ifTrue:[
+ and:[ node stop <= nodeParent temporaries last stop ]]].
+
+ nodeIsInBlockArguments :=
+ node blockScope notNil
+ and:[ node blockScope arguments notEmptyOrNil
+ and:[ node stop <= node blockScope arguments last stop ]].
+
+ (nodeIsInBlockArguments not and:[rememberedScopeNodes notNil]) ifTrue:[
+ "/ sigh - parent (and therefore blockScope) is unknown if parser has error
+ nodeIsInBlockArguments :=
+ rememberedScopeNodes
+ contains:[:scope |
+ (scope isMethod or:[scope isBlock])
+ and:[scope arguments notEmpty
+ and:[scope arguments first start <= node start
+ and:[scope arguments last stop >= node stop]]].
+ ].
+ ].
+ nodeIsInTemporaries ifTrue:[ ^ self ]. "/ no completion in a tempvar decl
+ nodeIsInBlockArguments ifTrue:[ ^ self ]. "/ no completion in a tempvar decl
+
+ "/ for variable completion, cursor must be right after the node
+ codeView characterPositionOfCursor = (node stop + 1) ifTrue:[
+ codeView characterBeforeCursor ~= Character space ifTrue:[
self codeCompletionForVariable:node into:actionBlock.
^ self.
- ]
- ].
+ ].
+ ].
+ ].
+
false ifTrue:[
codeView characterPositionOfCursor = (node stop + 2) ifTrue:[
+ |classes cls|
"/ after a variable;
"/ offer local messages, if receiver type is known
classes := (self classesOfNode:node).
@@ -4081,8 +4311,6 @@
]
]
].
-^ self
- ].
node isLiteral ifTrue:[
"/ however, user may want to complete a symbol inside a literal array!!
@@ -4131,32 +4359,141 @@
nodeParent := node parent.
].
+
+ "/ Transcript show:'node is ';showCR:node.
+
"/ move outward, until we find a message-send node,
"/ or the method's selector pattern node.
checkedNode := node.
[checkedNode notNil] whileTrue:[
(characterPositionOfCursor < (checkedNode stop ? source size)) ifTrue:[
- self information:'Inside a message node'.
- ^ self.
+Transcript show:'T: '; showCR:node.
+ "/ Transcript showCR:('Inside a ',(checkedNode className)).
+ self information:('Inside a ',(checkedNode className)).
+ (node isVariable or:[node isBlock and:[node stop notNil]]) ifTrue:[
+ characterPositionOfCursor == (node stop + 1) ifTrue:[
+ codeView characterBeforeCursor == Character space ifTrue:[
+ self codeCompletionForMessageTo:node into:actionBlock.
+ ^ self
+ ].
+ ].
+ characterPositionOfCursor == (node stop) ifTrue:[
+ "/ hack (spaces at end of line)
+ codeView characterBeforeCursor == Character space ifTrue:[
+ self codeCompletionForMessageTo:node into:actionBlock.
+ ^ self
+ ]
+ ].
+ ].
+
+ (checkedNode isMessage
+ and:[characterPositionOfCursor < (checkedNode selectorParts first start)]) ifTrue:[
+ self codeCompletionForMessageTo:checkedNode receiver into:actionBlock.
+ ^ self
+ ]
+
].
checkedNode isMessage ifTrue:[
"/ completion in a message-send
+ "/ Transcript showCR:'codeCompletionForMessage'.
self codeCompletionForMessage:checkedNode into:actionBlock.
^ self
].
checkedNode isMethod ifTrue:[
"/ completion in a method's selector pattern
+ "/ Transcript showCR:'codeCompletionForMethodSpec'.
self codeCompletionForMethodSpec:checkedNode into:actionBlock.
^ self.
].
+
+
checkedNode := checkedNode parent.
].
+ "/ Transcript showCR:'Node is neither variable nor message'.
self information:'Node is neither variable nor message.'.
"Modified: / 04-07-2006 / 18:48:26 / fm"
- "Modified: / 16-09-2011 / 14:54:47 / cg"
+ "Modified: / 01-05-2016 / 17:55:11 / cg"
+!
+
+valueAndKindOfVariable:aVariableName
+ "when showing possible completions for a variable,
+ it is a good idea to know what the reveiver's value is.
+ Sigh - returns nil both if unknown AND if a real nil is there."
+
+ |nodeVal con privateClass pool|
+
+ aVariableName isUppercaseFirst ifTrue:[
+ classOrNil notNil ifTrue:[
+ (classOrNil theNonMetaclass classVarNames includes:aVariableName) ifTrue:[
+ nodeVal := classOrNil theNonMetaclass classVarAt:aVariableName.
+ ^ { nodeVal . #classVariable }
+ ].
+ privateClass := classOrNil theNonMetaclass privateClasses detect:[:cls | cls nameWithoutPrefix = aVariableName] ifNone:nil.
+ privateClass notNil ifTrue:[
+ nodeVal := privateClass.
+ ^ { nodeVal . #privateClass }
+ ].
+ pool := classOrNil theNonMetaclass sharedPools detect:[:pool | pool classVariableNames includes:aVariableName] ifNone:nil.
+ pool notNil ifTrue:[
+ nodeVal := pool classVarAt:aVariableName.
+ ^ { nodeVal . #poolVariable }
+ ].
+ ].
+ aVariableName knownAsSymbol ifTrue:[
+ nodeVal := Smalltalk at:aVariableName asSymbol.
+ nodeVal notNil ifTrue:[
+ ^ { nodeVal . #global }
+ ]
+ ].
+
+ "/ 'evaluate' the variable (like in a browser's codeView)
+ "/ mhmh - will we catch workspace vars then?
+ Error handle:[:ex |
+ ] do:[
+ nodeVal := Parser new evaluate:aVariableName in:classOrNil receiver:classOrNil.
+ ].
+ nodeVal notNil ifTrue:[
+ ^ { nodeVal . #global }
+ ].
+ ^ nil
+ ].
+
+ aVariableName = 'self' ifTrue:[
+ (classOrNil notNil and:[classOrNil isMeta]) ifTrue:[
+ ^ { classOrNil theNonMetaclass . #pseudoVar }
+ ].
+ contextOrNil notNil ifTrue:[
+ ^ { contextOrNil receiver . #pseudoVar }
+ ].
+ ^ nil
+ ].
+
+ contextOrNil notNil ifTrue:[
+ con := contextOrNil.
+ [ con notNil ] whileTrue:[
+ "/ a local in the context?
+ ((con argAndVarNames ? #()) includes:aVariableName) ifTrue:[
+ nodeVal := con argsAndVars at:(con argAndVarNames indexOf:aVariableName) ifAbsent:nil.
+ nodeVal notNil ifTrue:[
+ ^ { nodeVal . #argument }
+ ].
+ ].
+ con := con home.
+ ].
+ "/ an instvar
+ (contextOrNil receiver class allInstVarNames includes:aVariableName) ifTrue:[
+ nodeVal := contextOrNil receiver instVarNamed:aVariableName.
+ nodeVal notNil ifTrue:[
+ ^ { nodeVal . #instanceVariable }
+ ].
+ ].
+ ].
+ ^ nil
+
+ "Created: / 01-05-2016 / 12:40:05 / cg"
!
valueOfNode:aNode
@@ -4213,58 +4550,15 @@
it is a good idea to know what the reveiver's value is.
Sigh - returns nil both if unknown AND if a real nil is there."
- |nodeVal con privateClass|
-
- aVariableName isUppercaseFirst ifTrue:[
- "/ simply 'evaluate' the variable (like in a browser's codeView)
- "/ mhmh - will we catch workspace vars then?
- Error handle:[:ex |
- ] do:[
- nodeVal := Parser new evaluate:aVariableName in:nil receiver:classOrNil.
- ].
- nodeVal notNil ifTrue:[
- ^ nodeVal
- ].
- classOrNil notNil ifTrue:[
- (classOrNil theNonMetaclass classVarNames includes:aVariableName) ifTrue:[
- nodeVal := classOrNil theNonMetaclass classVarAt:aVariableName.
- ^ nodeVal.
- ].
- privateClass := classOrNil theNonMetaclass privateClasses detect:[:cls | cls nameWithoutPrefix = aVariableName] ifNone:nil.
- privateClass notNil ifTrue:[
- nodeVal := privateClass.
- ^ nodeVal.
- ].
- ].
- ^ nil
- ].
- aVariableName = 'self' ifTrue:[
- (classOrNil notNil and:[classOrNil isMeta]) ifTrue:[^ classOrNil theNonMetaclass].
- contextOrNil notNil ifTrue:[^ contextOrNil receiver].
- ^ nil
- ].
-
- contextOrNil notNil ifTrue:[
- con := contextOrNil.
- [ con notNil ] whileTrue:[
- "/ a local in the context?
- ((con argAndVarNames ? #()) includes:aVariableName) ifTrue:[
- nodeVal := con argsAndVars at:(con argAndVarNames indexOf:aVariableName) ifAbsent:nil.
- nodeVal notNil ifTrue:[
- ^ nodeVal
- ].
- ].
- con := con home.
- ].
- "/ an instvar
- (contextOrNil receiver class allInstVarNames includes:aVariableName) ifTrue:[
- nodeVal := contextOrNil receiver instVarNamed:aVariableName.
- nodeVal notNil ifTrue:[
- ^ nodeVal
- ].
- ].
+ |valueAndKind|
+
+ (valueAndKind := self valueAndKindOfVariable:aVariableName) notNil ifTrue:[
+ self assert:valueAndKind isArray.
+ ^ valueAndKind first.
].
^ nil
+
+ "Modified: / 01-05-2016 / 12:41:30 / cg"
!
withoutSelectorsUnlikelyFor:aClass from:selectorsArg forPartial:partialSelector