1 AutoCompMethodBrowser {
2 classvar <>wWidth = 400, <>wHeight = 360;
3 classvar <methodExclusions, <classExclusions;
5 classvar <w, textField, listView, // gui objects
6 selector, // string typed by the user to match to classes or methods
7 masterList, // a list of all methods with that selector
8 reducedList, // only the ones displayed
9 skipThis, // flag: do (this, xxx) or (xxx) in argList
10 dropMeta, // flag: display Meta_ for metaclasses?
11 doc, // document from which this was created
12 start, size; // start and length of identifier in document
14 classvar overwriteOnCancel = false;
17 methodExclusions = methodExclusions.asArray;
20 // prevent certain method selector strings from being gui'ed during typing
21 // assumes that selectors are spelled correctly!
22 // startup.rtf should include AutoCompMethodBrowser.exclude([\value, \if, ...]);
23 *exclude { |selectorArray|
24 methodExclusions = methodExclusions ++ selectorArray.collect({ |sel| sel.asSymbol });
27 *new { arg start, size, doc;
28 // may only open a browser if there isn't one open already
29 (w.isNil and: { this.newCondition(start, size, doc) }).if({
30 ^this.init(start, size, doc)
32 w.notNil.if({ w.front });
37 *newCondition { arg start, size, doc;
39 selector = doc.string(start, size).asSymbol;
40 ^Document.allowAutoComp and: { methodExclusions.includes(selector).not }
43 *init { arg argStart, argSize, argDoc;
44 var displaySel, temp, initString;
45 skipThis = dropMeta = false;
46 selector = argDoc.string(argStart, argSize);
47 // if no string, abort
48 (selector.size == 0).if({ ^nil });
49 // if it's part of a class name,
50 (selector[0] >= $A and: { selector[0] <= $Z }).if({
51 AutoCompClassBrowser.classExclusions.includes(selector.asSymbol.asClass).not.if({
52 // identify classes containing that string
53 masterList = Class.allClasses.select({ |cl|
54 cl.isMetaClass and: { cl.name.asString.containsi(selector) }
55 }).collect({ |cl| // then grab their *new methods
56 [cl, cl.findRespondingMethodFor(\new), cl.name] // cl.name used for sorting
57 }).reject({ |item| item[1].isNil });
58 initString = selector;
60 skipThis = dropMeta = true;
61 overwriteOnCancel = true;
64 masterList = IdentitySet.new;
65 Class.allClasses.do({ |class|
66 class.methods.do({ |method|
67 method.name.asString.containsi(selector).if({
68 masterList.add([class, method,
69 class.name ++ "-" ++ method.name
75 displaySel = selector;
76 overwriteOnCancel = false;
78 (masterList.size > 0).if({
79 // this sort will sort both of them (desired)
80 // [0] is the ownerclass, [1] the method
81 masterList = masterList.asArray.sort({ |a, b| a[2] < b[2] });
82 // if previous char is a ., then class should not be displayed in argList
83 (argStart > 0 and: { argDoc.string(argStart-1, 1)[0] == $. }).if({ skipThis = true });
87 this.prInit("." ++ displaySel);
88 textField.string_(initString);
95 *free { |finished = false|
97 // if window is nil, isclosed should be true
98 // close the window only if it isn't closed
99 (w.tryPerform(\isClosed) ? true).not.if({
100 // if there's typing in the text box and no possible autocomplete,
101 // add it into the document
102 ((string = textField.string).size > 0 and: { finished.not }).if({
103 // does the string start with the selector?
104 // size var is size of selector
105 (string[0..size-1] == selector).if({
106 // if so, drop it so the rest can go in the document
107 string = string[size..];
109 // if not, and the selector is a class, we need to drop it from doc
110 overwriteOnCancel.if({
111 doc.selectRange(start, size+1)
114 doc.selectedString_(string);
118 // garbage; also, w = nil allows next browser to succeed
119 w = masterList = reducedList = nil;
123 var selectStart, selectSize, str;
124 // select the right text in the doc and replace with method template
125 (reducedList.size > 0).if({
126 doc.selectRange(start, size+1) // must replace open paren which size doesn't include
127 .selectedString_(str = this.finishString(reducedList[listView.value]));
128 #selectStart, selectSize = this.finalSelection(str);
129 doc.selectRange(selectStart, selectSize); // reposition cursor
130 textField.string_(""); // .free will do something bad if I don't clear this
135 *finalSelection { |str|
136 var openParen, closeParen;
137 (openParen = str.detectIndex({ |ch| ch.ascii == 40 })).isNil.if({
138 ^[start + str.size, 0]
140 closeParen = str.detectIndex({ |ch| ch.ascii == 41 });
141 ^[openParen + 1 + start, closeParen-openParen-1]
145 *finishString { |meth|
146 ^dropMeta.if({ meth[0].name.asString.copyRange(5, 2000) },
148 ++ meth[1].argList(skipThis)
151 *itemList { arg mList;
152 ^mList.collect({ |meth|
157 *listItem { arg meth;
158 ^dropMeta.if({ meth[0].name.asString.copyRange(5, 2000) },
160 ++ "-" ++ meth[1].name ++ meth[1].argList(skipThis)
164 var str, nametemp, keep;
165 str = textField.string;
166 reducedList = masterList.select({ |item|
168 nametemp = this.listItem(item);
169 // if str is 0 length, loop doesn't execute and all items will be kept
171 (nametemp[i] != chr).if({ keep = false });
175 listView.items_(this.itemList(reducedList)).value_(0);
180 \CocoaGUI.asClass.notNil.if({
181 gui = CocoaGUI; // maybe GUI.current will be supportable later
182 boundsTemp = gui.window.screenBounds;
183 // center the window on screen
184 w = gui.window.new(title, Rect(
185 (boundsTemp.width - wWidth) / 2, (boundsTemp.height - wHeight) / 2,
187 )).onClose_({ this.free });
188 gui.staticText.new(w, Rect(5, 25, wWidth-10, 20))
189 .string_("Type a bit or click and [cr] in the list");
190 // 3.2 -> 3.3 transition hack - will hardcode SCTextFieldOld later
191 textField = ('SCTextFieldOld'.asClass ?? { SCTextField })
192 .new(w, Rect(5, 50, wWidth - 10, 20)).resize_(2);
193 listView = gui.listView.new(w, Rect(5, 75, wWidth - 10, wHeight - 80))
195 .keyDownAction_({ |listV, char, modifiers, keycode|
197 { (modifiers bitAnd: 10485760 > 0) and: (keycode == 63232) }
198 { listView.value = (listView.value - 1) % reducedList.size }
199 { (modifiers bitAnd: 10485760 > 0) and: (keycode == 63233) }
200 { listView.value = (listView.value + 1) % reducedList.size } { char.ascii == 13 } { GUI.use( gui, {this.finish })}
201 { char.ascii == 27 } { this.free }
204 textField.keyDownAction_({ |txt, char, modifiers, unicode|
206 { (modifiers bitAnd: 10485760 > 0) and: (unicode == 63232) }
207 { listView.value = (listView.value - 1) % reducedList.size }
208 { (modifiers bitAnd: 10485760 > 0) and: (unicode == 63233) }
209 { listView.value = (listView.value + 1) % reducedList.size }
210 { char.ascii == 13 } { GUI.use( gui, { this.finish })}
211 { char.ascii == 27 } { this.free }
213 { txt.defaultKeyDownAction(char, modifiers, unicode);
214 this.restrictList(txt.string);
217 .action_({ GUI.use( gui, {this.finish })})
225 AutoCompClassBrowser : AutoCompMethodBrowser {
229 classExclusions = classExclusions.asArray;
232 // an actual array of classes, not symbols: [Nil, Boolean, True, False...]
233 // unforgiving of typos!
234 *exclude { |classArray|
235 classExclusions = classExclusions ++ (classArray.collect({ |cl|
236 [cl, ("Meta_" ++ cl.name).asSymbol.asClass]
240 *newCondition { arg start, size, doc;
242 class = ("Meta_" ++ doc.string(start, size)).asSymbol;
243 ^(Document.allowAutoComp and:
244 { classExclusions.includes(class.asClass).not } and: { Class.allClasses.detectIndex({ |cl| cl.name == class }).notNil })
247 *init { arg argStart, argSize, argDoc;
248 // newCondition determines that the class exists, so now it should be
249 // ok to interpret the string to get the class
250 savedClass = ("Meta_" ++ argDoc.string(argStart, argSize)).interpret;
251 reducedList = masterList = this.getMethods(savedClass).sort({ |a, b|
257 this.prInit(savedClass.name);
259 overwriteOnCancel = false;
262 *getMethods { arg class;
263 var list, existsFlag;
265 // loop through superclasses, but only Meta_ classes allowed
266 { class != Class }.while({
267 // only if this class is not excluded
268 classExclusions.includes(class).not.if({
270 class.methods.do({ |meth|
271 // does this method name already exist in the list?
272 // this accounts for subclasses overriding superclass methods
274 list.do({ |item| (item.name == meth.name).if({ existsFlag = true }); });
275 // if not, add to the list
276 existsFlag.not.if({ list.add(meth); });
279 class = class.superclass;
284 *finishString { |meth|
286 strtemp = savedClass.name ++ "." ++ meth.name ++ meth.argList;
287 ^strtemp.copyRange(5, strtemp.size-1);
290 *listItem { arg meth;
291 ^meth.name ++ meth.argList
296 // need to think about this some more
297 // what if user deletes chars but doesn't add?
298 // maybe best solution is *free below -- never add chars
299 // ok because user will not type ctrl-. in normal use
301 AutoCompClassSearch : AutoCompClassBrowser {
302 classvar classBrowser; // reserve one class browser for autocomplete use
303 // userEditedString = false;
305 *newCondition { ^Document.allowAutoComp } // no restrictions on when a window will be opened
307 *init { arg argStart, argSize, argDoc;
308 selector = argDoc.string(argStart, argSize);
309 reducedList = masterList = Class.allClasses.reject({ |cl|
310 cl.isMetaClass or: { classExclusions.includes(cl) } })
311 .select({ |cl| cl.name.asString.containsi(selector) })
312 .sort({ |a, b| a.name < b.name });
316 this.prInit("Open a class browser");
317 textField.string_(selector);
318 // userEditedString = false;
322 *free { super.free(true) }
325 // I need to test whether the method chosen is in a superclass of the class
326 // selected here -- see finishString
327 savedClass = reducedList[listView.value];
328 classBrowser = savedClass.browse;
329 // set enter key action to drop a method template into the document
330 // methodView's enter key action can change
331 // based on what you do in the browser;
332 // save the custom action into the browser instance's environment
333 // so that this customization persists
334 classBrowser.views.put(\methodEnterKeyAction, {
335 var str, newstart, newsize;
336 // can't close unless a method is really chosen
337 if(classBrowser.currentMethod.notNil) {
338 doc.selectRange(start, size)
339 .selectedString_(str = this.finishString);
340 #newstart, newsize = this.finalSelection(str);
341 doc.selectRange(newstart, newsize);
345 classBrowser.views.methodView.focus
346 .enterKeyAction_(classBrowser.views[\methodEnterKeyAction])
347 .keyDownAction_({ |list, char, modifiers, unicode|
349 classBrowser.views.superButton.doAction;
351 list.defaultKeyDownAction(char, modifiers, unicode)
359 meth = classBrowser.currentMethod;
360 if(meth.ownerClass.isMetaClass) {
361 // if the method chosen comes from this class or a superclass,
362 // use this class, otherwise use the class selected in the class browser
363 if(savedClass.metaclass == meth.ownerClass
364 or: { savedClass.metaclass.superclasses.includes(meth.ownerClass) }) {
365 classname = savedClass.name
367 classname = meth.ownerClass.name.asString.copyRange(5, 2000)
370 ++ "." ++ meth.name ++ meth.argList
372 ^meth.name ++ meth.argList