Reputation: 419
Suppose I have a multi-line statement like this:
1 to: 5 do: [:i|
Transcript show: i.
Transcript cr].
Currently, when I put a text cursor on some line (without selecting anything) and press Cmd+d, Pharo tries to execute the current line. But it would be more convenient for me if by default (when nothing is selected) Pharo would execute the current statement (i.e. all this three-line statement), not just the current line. Because this is a much more frequent case ("I want to execute the whole statement") than "I want to execute this particular line inside a statement" (which in most cases just doesn't make sense syntactically, as 1st and 3rd lines here). And in these rear occasions (when I need to execute a line inside a statement) I would pre-select this line manually.
How can I achieve this?
Upvotes: 5
Views: 274
Reputation: 14843
Here is the idea for an algorithm. You will need to improve and complete it.
Define a class ExpressionFinder
for finding the proper expression in your text.
In my sketch this class has the following ivars
string
:
the complete string in your pane (playground/transcript/whatever)compiler
:
the compiler used by your pane to evaluate textlines
: the collection of associations pos->line
, where pos
is the position of the line
inside string
index
: current index to the lines
collection used by the algorithminterval
: the output interval if any, otherwise nil
Assume you are given the string
, the compiler
and the current position
of the cursor on string
. Do the following:
string: aString position: anInteger compiler: aCompiler
string := aString.
compiler := aCompiler.
self computeLines.
index := lines findLast: [:assoc | assoc key <= anInteger]
Here is how you compute the collection of lines:
computeLines
| reader |
lines := OrderedCollection new.
reader := string readStream.
[reader atEnd]
whileFalse: [lines add: reader position + 1 -> reader nextLine]
With all of this you have everything you need to find the appropriate fragment. Here is a simple idea (which you should improve):
Start at the current line index and find the fragment by adding a line at a time. If found, end. If not, decrease the index and try again from the line above.
Here is the code
find
| i |
i := index.
[
i <= 0 ifTrue: [^self].
assoc := lines at: i.
self findFrom: assoc key]
whileFalse: [i := i - 1]
where
findFrom: start
| i end success |
i := index.
[| assoc fragment |
assoc := lines at: i + 1 ifAbsent: [string size + 1 -> nil].
end := assoc key - 1.
fragment := string copyFrom: start to: end.
success := self canCompile: fragment.
success not and: [end < string size]]
whileTrue: [i := i + 1].
success ifTrue: [interval := start to: end].
^success
The code for canCompile: fragment
is dialect-dependent, on the lines of
canCompile: fragment
^(compiler compileExpression: fragment) notNil
If your compiler signals CompilationErrors
, you will need to put a handler in canCompile:
to avoid them. Also you might take advantage of such errors. For instance, if the compilation error refers to an undeclared variable, you know that you will not find its definition in the lines below, so you should exit the loop in findFrom:
so to try with the line above and so on.
Upvotes: 0
Reputation: 376
To answer your question: Take a look at the text component. It has some method for evaluate-selection-and-do. And if nothing is selected, it tries to select the current line. You may change this implementation to find the top most statement "scope". It could be possible if you work with the code AST instead of the text. I worked once with this, to make it smarter for code expressions inside of comments.(that didn't work for all situations because the context for getting the method AST isn't always the same for this text component,in different tools (browser/workspace/and other))
Upvotes: 1