Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/Gt4beam/ElixirBodySubstringFilter.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
Class {
#name : 'ElixirBodySubstringFilter',
#superclass : 'ElixirFunctionsFilter',
#instVars : [
'substring'
],
#category : 'Gt4beam-Filters',
#package : 'Gt4beam',
#tag : 'Filters'
}

{ #category : 'instance creation' }
ElixirBodySubstringFilter class >> forSubstring: value [
^ self new substring: value
]

{ #category : 'descriptors' }
ElixirBodySubstringFilter class >> globalFilterDescriptor2 [
^ GtFilterTextModel new
creator: [ :value | self forSubstring: value ];
named: 'Substring';
order: 10;
yourself
]

{ #category : 'descriptors' }
ElixirBodySubstringFilter >> filterDescriptor2For: aMethodsCoder [
^ (super filterDescriptor2For: aMethodsCoder)
ifNotNil: [ :aFilterModel |
aFilterModel
text: substring;
yourself ]
]

{ #category : 'accessing' }
ElixirBodySubstringFilter >> filterValueString [
^ self substring
]

{ #category : 'highlighting' }
ElixirBodySubstringFilter >> highlightRangesIn: aString [
^ self rangesOf: self substring in: aString
]

{ #category : 'testing' }
ElixirBodySubstringFilter >> matches: anEntry [
^ (anEntry attributeAt: #source) asString asLowercase
includesSubstring: self substring asLowercase
]

{ #category : 'accessing' }
ElixirBodySubstringFilter >> substring [
^ substring
]

{ #category : 'accessing' }
ElixirBodySubstringFilter >> substring: anObject [
substring := anObject
]
9 changes: 9 additions & 0 deletions src/Gt4beam/ElixirFunctionCoder.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,15 @@ ElixirFunctionCoder >> hasSource [
^ startLine isNotNil and: [ startLine > 0 ]
]

{ #category : 'add-ons' }
ElixirFunctionCoder >> highlightFiltersFor: anAst into: coderAddOns [
"Underline the active substring filters matches in the source. A <gtAstCoderAddOns:>
pragma (not initializeAddOns:) so it is re-collected + re-run on every addOns rebuild."

<gtAstCoderAddOns: 11>
coderAddOns addStyler: ElixirSubstringHighlightStyler new
]

{ #category : 'as yet unclassified' }
ElixirFunctionCoder >> initializeAddOns: addOns [
super initializeAddOns: addOns.
Expand Down
68 changes: 68 additions & 0 deletions src/Gt4beam/ElixirFunctionFilterExamples.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"
I demonstrate the Functions-list filters' pure logic: kind
normalisation, substring matching, and the match/highlight agreement.
"
Class {
#name : 'ElixirFunctionFilterExamples',
#superclass : 'Object',
#category : 'Gt4beam-Examples',
#package : 'Gt4beam',
#tag : 'Examples'
}

{ #category : 'examples' }
ElixirFunctionFilterExamples >> bodyFilterMatchesCaseInsensitively [
"The Body filter finds a substring anywhere in the source,
regardless of case, like its Name sibling."

<gtExample>
| filter |
filter := ElixirBodySubstringFilter forSubstring: 'MNESIA'.
self assert: (filter matches: (self entryNamed: 'go' kind: 'def'
source: 'def go, do: :mnesia.transaction(fn -> :ok end)')).
self assert: (filter matches: (self entryNamed: 'go' kind: 'def'
source: 'def go, do: :ets.lookup(:t, :k)')) not.
^ filter
]

{ #category : 'support' }
ElixirFunctionFilterExamples >> entryNamed: aName kind: aKind source: aSource [
^ BeamFunctionEntry fields: (Dictionary new
at: 'name' put: aName;
at: 'kind' put: aKind;
at: 'source' put: aSource;
yourself)
]

{ #category : 'examples' }
ElixirFunctionFilterExamples >> kindFilterNormalisesKindShapes [
"The bridge sends kind as 'def'/'type' or #def/#type depending on
the eval path; entryKind: strips the # so one filter covers both."

<gtExample>
| filter |
filter := ElixirKindFilter forKind: 'Types'.
self assert: (filter matches: (self entryNamed: 't' kind: 'type' source: '@type t :: atom')).
self assert: (filter matches: (self entryNamed: 't' kind: '#type' source: '@type t :: atom')).
self assert: (filter matches: (self entryNamed: 'go' kind: 'def' source: 'def go, do: :ok')) not.
^ filter
]

{ #category : 'examples' }
ElixirFunctionFilterExamples >> nameFilterHighlightAgreesWithMatch [
"What matches is what lights up: an uppercase query matches a
lowercase name AND highlights it; the ranges index the original
string."

<gtExample>
| filter source ranges |
filter := ElixirNameSubstringFilter forSubstring: 'GET'.
self assert: (filter matches: (self entryNamed: 'get_user' kind: 'def'
source: 'def get_user, do: :ok')).
source := 'def get_user, do: forget()'.
ranges := filter highlightRangesIn: source.
self assert: ranges size equals: 2.
ranges do: [ :r |
self assert: (source copyFrom: r first to: r last) asLowercase equals: 'get' ].
^ filter
]
31 changes: 31 additions & 0 deletions src/Gt4beam/ElixirFunctionFiltersBuilder.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Class {
#name : 'ElixirFunctionFiltersBuilder',
#superclass : 'Object',
#instVars : [
'coders'
],
#category : 'Gt4beam-Filters',
#package : 'Gt4beam',
#tag : 'Filters'
}

{ #category : 'accessing' }
ElixirFunctionFiltersBuilder >> availableFilters [
"All Elixir function filters' descriptor-2 models, sorted by order (the + menu source)."
| available |
coders ifNil: [ ^ #() ].
available := SortedCollection sortBlock: [ :a :b | a order < b order ].
ElixirFunctionsFilter withAllSubclassesDo: [ :each |
each filterDescriptors2For: coders into: available ].
^ available
]

{ #category : 'accessing' }
ElixirFunctionFiltersBuilder >> coders [
^ coders
]

{ #category : 'accessing' }
ElixirFunctionFiltersBuilder >> coders: aCoders [
coders := aCoders
]
26 changes: 19 additions & 7 deletions src/Gt4beam/ElixirFunctionStreamingModel.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,10 @@ ElixirFunctionStreamingModel >> newCoderFor: anEntry [

{ #category : 'streaming' }
ElixirFunctionStreamingModel >> newItemsStream [
"I return function entries as an async stream in the order
GtBridge.Analysis.all_functions/1 emits them. The BEAM-side
already groups default-arg arity siblings (start: 0) above
their AST counterpart and parks orphan macro-generated entries
at the end, so re-sorting by start would just lump every
start: 0 entry at the top."
^ moduleCoder functionEntries asAsyncStream
"additionalFilters (not compositeFilter): the null main filter isn't a
predicate, and entries keep all_functions/1 order via select:."
^ (moduleCoder functionEntries select: [ :e |
self additionalFilters allSatisfy: [ :f | f matches: e ] ]) asAsyncStream
]

{ #category : 'announcements' }
Expand All @@ -78,6 +75,12 @@ ElixirFunctionStreamingModel >> onModuleSourceCodeChanged: anAnnouncement [
self refreshStream
]

{ #category : 'streaming' }
ElixirFunctionStreamingModel >> refreshItemsStreamDueTo: aReason [
super refreshItemsStreamDueTo: aReason.
self restyleHighlightCoders
]

{ #category : 'as yet unclassified' }
ElixirFunctionStreamingModel >> refreshStream [
"Drops cached entries whose key isn't in the fresh set —
Expand All @@ -96,3 +99,12 @@ ElixirFunctionStreamingModel >> refreshStream [
(freshKeys includes: k) ifFalse: [ coders removeKey: k ] ] ].
self refreshItemsStreamDueTo: 'functions reordered'
]

{ #category : 'streaming' }
ElixirFunctionStreamingModel >> restyleHighlightCoders [
"Rebuild each cached coder's addOns so the <gtAstCoderAddOns:> highlight styler re-reads
the active substrings and re-underlines (or clears) after a filter change."

(mutex critical: [ coders values asArray ])
do: [ :coder | coder requestUpdateAddOns ]
]
65 changes: 65 additions & 0 deletions src/Gt4beam/ElixirFunctionsFilter.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Class {
#name : 'ElixirFunctionsFilter',
#superclass : 'GtSearchFilter',
#category : 'Gt4beam-Filters',
#package : 'Gt4beam',
#tag : 'Filters'
}

{ #category : 'descriptors' }
ElixirFunctionsFilter class >> filterDescriptors2For: aCoder into: aCollection [
(self filterDescriptor2For: aCoder)
ifNotNil: [ :aDescriptor | aCollection add: aDescriptor ]
]

{ #category : 'comparing' }
ElixirFunctionsFilter >> = anObject [
"Value-aware (stock GtSearchFilter compares class only) so a changed value re-applies."
^ self class = anObject class and: [ self filterValueString = anObject filterValueString ]
]

{ #category : 'matching' }
ElixirFunctionsFilter >> entryKind: anEntry [
"Normalised kind, '#' stripped: the bridge sends it as 'def'/'type'
(ByteString) or #def/#type (BeamAtomObject) depending on the path."
^ (anEntry attributeAt: #kind)
ifNil: [ '' ]
ifNotNil: [ :k | k asString copyWithout: $# ]
]

{ #category : 'comparing' }
ElixirFunctionsFilter >> hash [
^ self class hash bitXor: self filterValueString hash
]

{ #category : 'highlighting' }
ElixirFunctionsFilter >> highlightRangesIn: aString [
"Source ranges I want highlighted. None by default; substring filters override."
^ #()
]

{ #category : 'matching' }
ElixirFunctionsFilter >> isType: anEntry [
^ #('type' 'opaque' 'typep') includes: (self entryKind: anEntry)
]

{ #category : 'matching' }
ElixirFunctionsFilter >> matches: anEntry [
^ true
]

{ #category : 'highlighting' }
ElixirFunctionsFilter >> rangesOf: aSubstring in: aString [
"Case-insensitive, agreeing with the filters' matches:. Scans
lowered copies; the ranges index the original string."
| ranges idx lowered target |
(aSubstring isNil or: [ aSubstring isEmpty ]) ifTrue: [ ^ #() ].
lowered := aSubstring asLowercase.
target := aString asLowercase.
ranges := OrderedCollection new.
idx := target indexOfSubCollection: lowered startingAt: 1.
[ idx > 0 ] whileTrue: [
ranges add: (idx to: idx + lowered size - 1).
idx := target indexOfSubCollection: lowered startingAt: idx + 1 ].
^ ranges
]
56 changes: 56 additions & 0 deletions src/Gt4beam/ElixirKindFilter.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Class {
#name : 'ElixirKindFilter',
#superclass : 'ElixirFunctionsFilter',
#instVars : [
'kind'
],
#category : 'Gt4beam-Filters',
#package : 'Gt4beam',
#tag : 'Filters'
}

{ #category : 'instance creation' }
ElixirKindFilter class >> forKind: aString [
^ self new kind: aString
]

{ #category : 'descriptors' }
ElixirKindFilter class >> globalFilterDescriptor2 [
^ GtFilterShortListModel new
creator: [ :item | self forKind: item itemValue ];
items: [ #('All' 'Functions' 'Types') ];
selectedItem: 'All';
displayAllItems;
name: 'Kind';
order: 20;
beDefault;
yourself
]

{ #category : 'descriptors' }
ElixirKindFilter >> filterDescriptor2For: aCoder [
^ (super filterDescriptor2For: aCoder)
ifNotNil: [ :aFilterModel | aFilterModel selectedItem: kind; yourself ]
]

{ #category : 'accessing' }
ElixirKindFilter >> filterValueString [
^ kind
]

{ #category : 'accessing' }
ElixirKindFilter >> kind [
^ kind
]

{ #category : 'accessing' }
ElixirKindFilter >> kind: aString [
kind := aString
]

{ #category : 'matching' }
ElixirKindFilter >> matches: anEntry [
kind = 'Types' ifTrue: [ ^ self isType: anEntry ].
kind = 'Functions' ifTrue: [ ^ (self isType: anEntry) not ].
^ true
]
Loading