Hi all,
While trying to dissociate the RB's ui, in order to use it for other browsers, like OmniBrowser and Whisker, I have come up with a more general Service design (and a part of the implementation), that I'd like to share with you, to know if you agree with it, or have propositions to enhance it.
The goal is to have a registry of services, classified by categories, and usable by menus, buttons, keystrokes (linking with the KeyMapper should be possible), and a kind of commandline like Vim's one or Emacs' minibuffer (won't you emacs freaks like to type : M-x extract-method ?;-)))
Categories should mainly be used by menus, to display relevant actions, otherwise services could be triggered by the commandline or by shortcuts from nearly anywhere (M-x close-window, Alt-w c ...).
Categories of services registered could be : -World Menu services -Open/Help/Appearance/Windows/... Menu services (subcategories of the world menu) -Class services -Methods services -Text Selection services -Refactoring services, as subcategories of the Class/Methods and Text Selection services -Browser's button bar services.
This way adding services to the system could be much easier. This could permit other services as : - Typing services, using the RefactoryTyper - BrowseUnit services for my case, ....
I've also looked at the FileList services, and I think I have a more general solution.
So here would be the way to define a service
ExtractMethodRefactoring >> service : "answer a service using the current code selection to extract it" ^ Service new label: 'Extract method'; description: 'Extracts the current code selection, in a new method using the necessary parameters, replacing the selection with a self message send.' action: [:class :selector :selection | (self extract: selection from: selector in: class) execute]
These would be the default parameters to provide, optional one includes :
condition: [:selectionNode | selectionNode isMessage] Precondition for the service to execute/ to be displayed in a menu.
shortLabel: 'extract'
shorter label for a button / label is for a menu/ or for the command name
answerDo: [:answer | self inform: 'extraction was succesfull'] if the service user wants to use the result of the service
Now you must be wondering how to fetch the parameters to fill the blocks used. The idea would be to use a Requestor object, using the #requestor: message, which wil act as an interface between the service user and the service :
For each of the arguments needed by the service (for the refactoring service, these would be : selectionNode, and then class, selector, selection), a method is triggered in the Requestor : requestSelectionNode, requestClass, requestSelector, requestSelection.
The Requestor base class handles them in a default way, asking the users for the parameters, via FillInTheBlanks at the moment, and is able to handle unknow requests in a default way.
The requestor can them be subclassed to handle particular queries, i.e
BrowserRequestor >> requestClass ^ myBrowser selectedClassOrMetaclass
BrowserRequestor >> requestSelector ^ myBrowser selectedMessageName
BrowserRequestor >> requestSelection ^ myBrowser selectionInterval
FileRequestor >> requestFileName
WindowRequestor >> requestSystemWindow ^ myWindow
Requestor >> requestSystemWindow "pop up a window list and ask the user for a window"
The service would be used by an object by finding them, querying the Service class for available services or service categories, giving the services the object's requestor, and displaying them.
What do you think of this (If you read this far that is) ?
So far I have implemented Services and their displaying as menu and buttons, Some requestor classes and methods, tested them with various services (The ExtractMethodRefactoring works with the basic requestor and the browser requestor for example, asking the user for parameters in one case, fetching them in the current browsed method for the other), and I am implementing services categories and the service registry. Remaining works includes referencing a lot of services (senders/implementors, the world menu ...) but the refactorings are working with a previous version of the stuff, so they won't be hard to include.
Thanks for any feedback, Romain
On Mar 16, 2004, at 12:56 AM, Romain Robbes wrote:
What do you think of this (If you read this far that is) ?
Since nobody else is commenting: I like it from what I can tell reading your description; the proof, of course, will be in the implementation.
One thing that would be interesting is some way to associate service objects with packages, and get them stored/loaded as proper MC definition objects (the recent DoItParser support should make that easy enough). Then we wouldn't have to bother with #initialize methods, we could just directly register service objects and let MC do the rest. We'd also get proper merging if you and I concurrently add services to the same package, etc.
This implies a mechanism for registering extra objects with a PackageInfo instance, I think; I'd be willing to add this if the idea sounds good. I'm just picturing
(PackageInfo named: 'Foo') registerItem: anItem
where anItem as to respond to #asMonticelloDefinition, say.
Le 17 mars 04, à 02:07, Avi Bryant a écrit :
On Mar 16, 2004, at 12:56 AM, Romain Robbes wrote:
What do you think of this (If you read this far that is) ?
Since nobody else is commenting: I like it from what I can tell reading your description; the proof, of course, will be in the implementation.
Well I'm doing it , stay tuned ... I could post something soon if I solve a bug.
One thing that would be interesting is some way to associate service objects with packages, and get them stored/loaded as proper MC definition objects (the recent DoItParser support should make that easy enough). Then we wouldn't have to bother with #initialize methods, we could just directly register service objects and let MC do the rest. We'd also get proper merging if you and I concurrently add services to the same package, etc.
This implies a mechanism for registering extra objects with a PackageInfo instance, I think; I'd be willing to add this if the idea sounds good. I'm just picturing
(PackageInfo named: 'Foo') registerItem: anItem
where anItem as to respond to #asMonticelloDefinition, say.
That would be nice indeed ...
Sounds very Macintosh OS X NextStep-ish. I like it OK.
On Mar 16, 2004, at 1:56 AM, Romain Robbes wrote:
Hi all,
While trying to dissociate the RB's ui, in order to use it for other browsers, like OmniBrowser and Whisker, I have come up with a more general Service design (and a part of the implementation), that I'd like to share with you, to know if you agree with it, or have propositions to enhance it.
The goal is to have a registry of services, classified by categories, and usable by menus, buttons, keystrokes (linking with the KeyMapper should be possible), and a kind of commandline like Vim's one or Emacs' minibuffer (won't you emacs freaks like to type : M-x extract-method ?;-)))
Categories should mainly be used by menus, to display relevant actions, otherwise services could be triggered by the commandline or by shortcuts from nearly anywhere (M-x close-window, Alt-w c ...).
Categories of services registered could be : -World Menu services -Open/Help/Appearance/Windows/... Menu services (subcategories of the world menu) -Class services -Methods services -Text Selection services -Refactoring services, as subcategories of the Class/Methods and Text Selection services -Browser's button bar services.
This way adding services to the system could be much easier. This could permit other services as :
- Typing services, using the RefactoryTyper
- BrowseUnit services for my case, ....
I've also looked at the FileList services, and I think I have a more general solution.
So here would be the way to define a service
ExtractMethodRefactoring >> service : "answer a service using the current code selection to extract it" ^ Service new label: 'Extract method'; description: 'Extracts the current code selection, in a new method using the necessary parameters, replacing the selection with a self message send.' action: [:class :selector :selection | (self extract: selection from: selector in: class) execute]
These would be the default parameters to provide, optional one includes :
condition: [:selectionNode | selectionNode isMessage] Precondition for the service to execute/ to be displayed in a menu.
shortLabel: 'extract'
shorter label for a button / label is for a menu/ or for the command
name
answerDo: [:answer | self inform: 'extraction was succesfull']
if the service user wants to use the result of the service
Now you must be wondering how to fetch the parameters to fill the blocks used. The idea would be to use a Requestor object, using the #requestor: message, which wil act as an interface between the service user and the service :
For each of the arguments needed by the service (for the refactoring service, these would be : selectionNode, and then class, selector, selection), a method is triggered in the Requestor : requestSelectionNode, requestClass, requestSelector, requestSelection.
The Requestor base class handles them in a default way, asking the users for the parameters, via FillInTheBlanks at the moment, and is able to handle unknow requests in a default way.
The requestor can them be subclassed to handle particular queries, i.e
BrowserRequestor >> requestClass ^ myBrowser selectedClassOrMetaclass
BrowserRequestor >> requestSelector ^ myBrowser selectedMessageName
BrowserRequestor >> requestSelection ^ myBrowser selectionInterval
FileRequestor >> requestFileName
WindowRequestor >> requestSystemWindow ^ myWindow
Requestor >> requestSystemWindow "pop up a window list and ask the user for a window"
The service would be used by an object by finding them, querying the Service class for available services or service categories, giving the services the object's requestor, and displaying them.
What do you think of this (If you read this far that is) ?
So far I have implemented Services and their displaying as menu and buttons, Some requestor classes and methods, tested them with various services (The ExtractMethodRefactoring works with the basic requestor and the browser requestor for example, asking the user for parameters in one case, fetching them in the current browsed method for the other), and I am implementing services categories and the service registry. Remaining works includes referencing a lot of services (senders/implementors, the world menu ...) but the refactorings are working with a previous version of the stuff, so they won't be hard to include.
Thanks for any feedback, Romain
Hi Romain,
I like it. Maybe you want to consider to deliver several services and not only one.
Something like
Object >> services ^OrderedCollection with: self service
should be easy to write and overwrite ... ;-) (I assume the ':' after "ExtractMethodRefactoring >> service" below is a typo)
Cheers,
Markus
Am Dienstag, 16.03.04 um 09:56 Uhr schrieb Romain Robbes:
So here would be the way to define a service
ExtractMethodRefactoring >> service : "answer a service using the current code selection to extract it" ^ Service new label: 'Extract method'; description: 'Extracts the current code selection, in a new method using the necessary parameters, replacing the selection with a self message send.' action: [:class :selector :selection | (self extract: selection from: selector in: class) execute]
On Mar 16, 2004, at 3:56 AM, Romain Robbes wrote:
Hi all,
While trying to dissociate the RB's ui, in order to use it for other browsers, like OmniBrowser and Whisker, I have come up with a more general Service design (and a part of the implementation), that I'd like to share with you, to know if you agree with it, or have propositions to enhance it.
I like it.
It's basically a more sophisticated version of the model that OmniBrowser is using now, so it would be easy to add service support to OB, which I'll do if you go ahead with this. (And even if you don't, I'll probably refactor OB to used this more elegant model.)
One thing you haven't described in detail is how service types are handled. I'd imagine that service providers should implement #servicesOfType: rather than just #service. No?
Colin
Here is a piece of code demonstrating the stuff I've come up.
As a proof of concept, I converted the refactorings as services, and added them in the basic Browser (bypassing the RB).
So : install the RB (Refactory-md.26.mcz) in a latest 3.7 image (5816) install Services.mcz evaluate :
Refactoring registerServices
to register all refactorings as services
open the browser (with alt-b on the world, else you'll got a real RB), or a hierarchy browser / message list browser
You should see the refactoring items in various menus : class list, message list, and code pane
Since the menus are inherited from the browser, you shall see them in the hierarchy/protocol browsers.
And you shall see the Breakpoint support menu item which I have never used, since it doesn't appear in the RB, that I may use now ;-) .
The code is located in the Services-Base and Services-RB packages, If you want to look at it . Note that as I am in a hurry, I didn't cleaned it thoroughly, so there are unused stuff in it ... look at the classes Service, ServiceCategory, Requestor and subclasses, and the Refactorings in priority.
There are a few ill-defined services for refactorings, so some of them might act weirdly (I didn't tested them all).
I'll post a cleaned-up version soon.
It's basically a more sophisticated version of the model that OmniBrowser is using now, so it would be easy to add service support to OB, which I'll do if you go ahead with this. (And even if you don't, I'll probably refactor OB to used this more elegant model.)
that was one of the goals ... you can try it if you want (wait for the clean version though)
One thing you haven't described in detail is how service types are handled. I'd imagine that service providers should implement #servicesOfType: rather than just #service. No?
yes, that's at the moments some hack around doesNotUnderstand and getting the name of variables in blocks (getting names of the arguments of anonymous functions ... strange :-) )
By the way I join a few fixes to the rb, making it a tad more usable (Markus will incorporate them soon)
Cheers, Romain
Colin
Romain,
I've finally gotten around to looking at your Services code, and I have one overriding comment, which is that although I like the model, the interface to it is too implicit - too magic. The best example is this, which I didn't understand when I first read it but do now:
Now you must be wondering how to fetch the parameters to fill the blocks used. The idea would be to use a Requestor object, using the #requestor: message, which wil act as an interface between the service user and the service :
For each of the arguments needed by the service (for the refactoring service, these would be : selectionNode, and then class, selector, selection), a method is triggered in the Requestor : requestSelectionNode, requestClass, requestSelector, requestSelection.
What you're saying is that when the service sees a block like
[:class | self doSomethingWith: class]
it will send #requestClass, before invoking that block, based on the name of the argument. I really don't like this - argument names should not affect behavior. I do realize that you're trying to make the interface as concise as possible, but I don't think the tradeoff is worth it in this case. I would much rather, for example, just get passed the requestor directly:
[:req | self doSomethingWith: req requestClass]
It's a handful more characters, but much easier to trace through, and doesn't suddenly break if the source code disappears for some reason...
I also find the use of strings as the main way to refer to services odd - I'd rather deal directly with the Service objects, and have them returned from some easily accessible method. For example, I'd prefer
TheWorldMenu openService execute
to
'open menu' executeService.
Cheers, Avi
Hi avi,
it will send #requestClass, before invoking that block, based on the name of the argument. I really don't like this - argument names should not affect behavior. I do realize that you're trying to make the interface as concise as possible, but I don't think the tradeoff is worth it in this case. I would much rather, for example, just get passed the requestor directly:
[:req | self doSomethingWith: req requestClass]
It's a handful more characters, but much easier to trace through, and doesn't suddenly break if the source code disappears for some reason...
Hmmm .... I got to think more about that ... I was also worried by the fact that it is tied to the source code. I think I'll experiment both versions by myself to see the tradeoffs.
I also find the use of strings as the main way to refer to services odd - I'd rather deal directly with the Service objects, and have them returned from some easily accessible method. For example, I'd prefer
TheWorldMenu openService execute
to
'open menu' executeService.
I've got to rework some things here too, I'm aware that using strings is a bit fragile.
Anyways thanks for the feedback, please continue ... ;-)
Cheers, Romain
squeak-dev@lists.squeakfoundation.org