1 // Since SC v3.2 dev, Document is an ABSTRACT class. Can't be instantiated directly.
2 // Subclasses provide the editor-specific implementation, e.g. CocoaDocument for the standard Mac interface.
3 // Subclasses also (in their SC code files) add a "implementationClass" method to Document to tell it to use them.
7 classvar <dir="", <wikiDir="", <allDocuments, >current;
8 classvar <>globalKeyDownAction, <> globalKeyUpAction, <>initAction;
10 classvar <>autoRun = true;
11 classvar <>wikiBrowse = true;
13 classvar <>implementationClass;
15 classvar <theme, <themes;
17 //don't change the order of these vars:
18 var <dataptr, <>keyDownAction, <>keyUpAction, <>mouseUpAction;
19 var <>toFrontAction, <>endFrontAction, <>onClose, <>mouseDownAction;
23 var <envir, savedEnvir;
32 num = this.numberOfOpen;
34 doc = this.newFromIndex(i);
36 postColor = Color.black;
39 classColor: Color(0, 0, 0.75, 1),
40 textColor: Color(0, 0, 0, 1),
41 stringColor: Color(0.375, 0.375, 0.375, 1),
42 commentColor: Color(0.75, 0, 0, 1),
43 symbolColor: Color(0, 0.45, 0, 1),
44 numberColor: Color(0, 0, 0, 1),
45 specialValsColor: Color(0.2, 0.2, 0.75, 1), // true false nil inf
46 specialVarsColor: Color(0.4, 0.4, 0.75, 1), // super, thisProcess
47 declColor: Color(0, 0, 1, 1), // var, const, args
48 puncColor: Color(0, 0, 0, 1),
49 environColor: Color(1.0, 0.4, 0, 1)
52 theme = themes.default;
55 *open { | path, selectionStart=0, selectionLength=0, envir |
57 env = currentEnvironment;
58 if(this.current.notNil) { this.current.restoreCurrentEnvironment };
59 doc = Document.implementationClass.prBasicNew.initFromPath(path, selectionStart, selectionLength);
63 currentEnvironment = env
68 *new { | title="Untitled", string="", makeListener=false, envir |
70 env = currentEnvironment;
71 if(this.current.notNil) { this.current.restoreCurrentEnvironment };
72 doc = Document.implementationClass.new(title, string, makeListener);
76 currentEnvironment = env
82 path = path.standardizePath;
83 if(path == "") { dir = path } {
84 if(pathMatch(path).isEmpty) { ("there is no such path:" + path).postln } {
91 path = path.standardizePath;
92 if(path == "") {wikiDir = path } {
93 if(pathMatch(path).isEmpty) { ("there is no such path:" + path).postln } {
99 *standardizePath { | p |
101 pathName = PathName(p.standardizePath);
102 ^if(pathName.isRelativePath) {
103 dir ++ pathName.fullPath
108 *abrevPath { | path |
109 if(path.size < dir.size) { ^path };
110 if(path.copyRange(0,dir.size - 1) == dir) {
111 ^path.copyRange(dir.size, path.size - 1)
120 *hasEditedDocuments {
121 allDocuments.do { | doc, i |
129 *closeAll { | leavePostWindowOpen = true |
130 allDocuments.do { | doc, i |
131 if(leavePostWindowOpen.not) {
134 if(doc.isListener.not) {
141 *closeAllUnedited { | leavePostWindowOpen = true |
143 allDocuments.do({ | doc, i |
145 listenerWindow = doc;
147 if(doc.isEdited.not, {
152 if(leavePostWindowOpen.not, {
153 listenerWindow.close;
158 if ( thisProcess.platform.hasFeature( \emacs ), {
159 ^this.implementationClass.current;
165 ^allDocuments[this.implementationClass.prGetIndexOfListener];
168 ^allDocuments.indexOf(this) == this.class.prGetIndexOfListener
177 this.prSetFileName(apath);
179 dir { var path = this.path; ^path !? { path.dirname } }
193 this.prSetTitle(argName);
196 background_ { | color |
197 this.prSetBackgroundColor(color);
202 this.prGetBackgroundColor(color);
206 selectedBackground_ { | color |
207 this.prSetSelectedBackgroundColor(color);
213 this.prGetSelectedBackgroundColor(color);
217 *postColor_ { | col |
219 ^Document.implementationClass.postColor_(col);
222 stringColor_ { | color, rangeStart = -1, rangeSize = 0 |
224 this.setTextColor(color,rangeStart, rangeSize);
227 ^this.prGetBounds(Rect.new);
229 bounds_ { | argBounds |
230 ^this.prSetBounds(argBounds);
240 ^this.subclassResponsibility(thisMethod)
244 ^this.subclassResponsibility(thisMethod)
247 alwaysOnTop_ { |boolean=true|
248 ^this.subclassResponsibility(thisMethod)
252 ^this.subclassResponsibility(thisMethod)
256 ^this.subclassResponsibility(thisMethod)
259 selectLine { | line |
260 this.prSelectLine(line);
263 selectRange { | start=0, length=0 |
264 ^this.subclassResponsibility(thisMethod)
267 editable_ { | abool=true |
269 this.prIsEditable_(abool);
272 ^this.subclassResponsibility(thisMethod)
275 promptToSave_ { | bool |
276 ^this.subclassResponsibility(thisMethod)
280 ^this.subclassResponsibility(thisMethod)
284 ^this.subclassResponsibility(thisMethod)
287 *setTheme { | themeName |
288 theme = themes[themeName];
289 thisProcess.platform.writeClientCSS;
290 Document.implementationClass.prSetSyntaxColorTheme(
297 theme.specialValsColor,
298 theme.specialVarsColor,
307 ^this.subclassResponsibility(thisMethod)
310 ^Document.current === this
314 ^this.selectedRangeLocation
318 ^this.selectedRangeSize
321 string { | rangestart, rangesize = 1 |
322 if(rangestart.isNil,{
325 ^this.rangeText(rangestart, rangesize);
328 string_ { | string, rangestart = -1, rangesize = 1 |
329 this.insertTextRange(string, rangestart, rangesize);
336 font_ { | font, rangestart = -1, rangesize=0 |
337 this.setFont(font, rangestart, rangesize)
340 selectedString_ { | txt |
341 this.prinsertText(txt)
345 ^this.getSelectedLines(this.selectionStart, 0);
348 getSelectedLines { | rangestart = -1, rangesize = 0 |
349 var start, end, str, max;
353 end = start + rangesize;
355 str[start] !== Char.nl and: { start >= 0 }
356 } { start = start - 1 };
358 str[end] !== Char.nl and: { end < max }
360 ^str.copyRange(start + 1, end);
366 this.class.current = this;
367 this.saveCurrentEnvironment;
368 toFrontAction.value(this);
372 endFrontAction.value(this);
373 this.restoreCurrentEnvironment;
376 makeWikiPage { | wikiWord, extension=(".rtf"), directory |
377 var filename, file, doc, string, dirName;
378 directory = directory ? wikiDir;
379 filename = directory ++ wikiWord ++ extension;
380 file = File(filename, "w");
382 string = "{\\rtf1\\mac\\ansicpg10000\\cocoartf102\\n{\\fonttbl}\n"
383 "{\\colortbl;\\red255\\green255\\blue255;}\n"
384 "Write about " ++ wikiWord ++ " here.\n}";
388 doc = this.class.open(filename);
390 doc.selectRange(0,0x7FFFFFFF);
392 if(doc.string == ("Write about " ++ wikiWord ++ " here.")) {
393 unixCmd("rm" + filename)
397 // in a second try, check if a path must be created.
398 // user makes double click on string.
399 dirName = wikiWord.dirname;
401 dirName = directory ++ dirName;
402 "created directory: % \n".postf(dirName);
409 var selectedText, filename, index, directory;
410 var extensions = #[".rtf", ".sc", ".scd", ".txt", "", ".rtfd", ".html"];
411 selectedText = this.selectedText;
412 index = this.selectionStart;
414 this.selectRange(index, 0);
416 // refer to local link with round parens
417 if(selectedText.first == $( /*)*/ and: {/*(*/ selectedText.last == $) }) {
418 selectedText = selectedText[1 .. selectedText.size-2];
419 directory = Document.current.path.dirname ++ "/";
424 case { selectedText[0] == $* }
427 selectedText = selectedText.drop(1);
428 extensions.do { |ext|
429 filename = directory ++ selectedText ++ ext;
430 if (File.exists(filename)) {
431 // open existing wiki page
436 filename = "Help/help-scripts/" ++ selectedText ++ ext;
437 if (File.exists(filename)) {
438 // open help-script document
445 { selectedText.first == $[ and: { selectedText.last == $] }}
448 selectedText[1 .. selectedText.size-2].openHelpFile
450 { selectedText.containsStringAt(0, "http://")
451 or: { selectedText.containsStringAt(0, "file://") } }
456 { selectedText.containsStringAt(selectedText.size-1, "/") }
458 Document(selectedText,
459 pathMatch(directory ++ selectedText).collect({ |it|it.basename ++ "\n"}).join
464 if(index + selectedText.size > this.text.size) { ^this };
465 extensions.do { |ext|
466 filename = directory ++ selectedText ++ ext;
467 if (File.exists(filename)) {
468 // open existing wiki page
469 this.class.open(filename);
473 // make a new wiki page
474 this.makeWikiPage(selectedText, nil, directory);
478 mouseUp{ | x, y, modifiers, buttonNumber, clickCount, clickPos |
479 mouseUpAction.value(this, x, y, modifiers, buttonNumber, clickCount); if (wikiBrowse and: { this.linkAtClickPos(clickPos).not }
480 and: { this.selectUnderlinedText(clickPos) } ) {
485 keyDown { | character, modifiers, unicode, keycode |
486 this.class.globalKeyDownAction.value(this,character, modifiers, unicode, keycode);
487 keyDownAction.value(this,character, modifiers, unicode, keycode);
490 keyUp { | character, modifiers, unicode, keycode |
491 this.class.globalKeyUpAction.value(this,character, modifiers, unicode, keycode);
492 keyUpAction.value(this,character, modifiers, unicode, keycode);
496 ^if(this.path.isNil or: { doc.path.isNil }) { doc === this } {
497 this.path == doc.path
502 ^(this.path ? this).hash
505 *defaultUsesAutoInOutdent_ {|bool|
506 Document.implementationClass.prDefaultUsesAutoInOutdent_(bool)
509 usesAutoInOutdent_ {|bool|
510 this.prUsesAutoInOutdent_(bool)
513 *prDefaultUsesAutoInOutdent_{|bool|
514 this.subclassResponsibility(thisMethod);
517 prUsesAutoInOutdent_{|bool|
518 ^this.subclassResponsibility(thisMethod);
522 // private implementation
524 prIsEditable_{ | editable=true |
525 ^this.subclassResponsibility(thisMethod)
527 prSetTitle { | argName |
528 ^this.subclassResponsibility(thisMethod)
531 ^this.subclassResponsibility(thisMethod)
534 ^this.subclassResponsibility(thisMethod)
536 prSetFileName { | apath |
537 ^this.subclassResponsibility(thisMethod)
539 prGetBounds { | argBounds |
540 ^this.subclassResponsibility(thisMethod)
543 prSetBounds { | argBounds |
544 ^this.subclassResponsibility(thisMethod)
547 *prSetSyntaxColorTheme{ |textC, classC, stringC, symbolC, commentC, numberC, specialValsC, specialVarsC, declC, puncC, environC|
548 ^this.subclassResponsibility(thisMethod);
551 // if range is -1 apply to whole doc
552 setFont { | font, rangeStart= -1, rangeSize=100 |
553 ^this.subclassResponsibility(thisMethod)
556 setTextColor { | color, rangeStart = -1, rangeSize = 0 |
557 ^this.subclassResponsibility(thisMethod)
561 ^this.subclassResponsibility(thisMethod)
564 ^this.subclassResponsibility(thisMethod)
566 selectUnderlinedText { | clickPos |
567 ^this.subclassResponsibility(thisMethod)
570 linkAtClickPos { | clickPos |
571 ^this.subclassResponsibility(thisMethod)
574 rangeText { | rangestart=0, rangesize=1 |
575 ^this.subclassResponsibility(thisMethod)
579 ^this.subclassResponsibility(thisMethod)
583 onClose.value(this); // call user function
584 this.restoreCurrentEnvironment;
585 allDocuments.remove(this);
589 prinsertText { | dataPtr, txt |
590 ^this.subclassResponsibility(thisMethod)
592 insertTextRange { | string, rangestart, rangesize |
593 ^this.subclassResponsibility(thisMethod)
597 allDocuments = allDocuments.add(this);
598 this.editable = true;
600 if (this.rangeText(0,7) == "/*RUN*/")
606 initAction.value(this);
610 //this is called after recompiling the lib
612 ^this.subclassResponsibility(thisMethod)
615 thisProcess.platform.when(\_NumberOfOpenTextWindows) {
617 } { ^allDocuments.size };
621 *newFromIndex { | idx |
622 ^super.new.initByIndex(idx)
624 initByIndex { | idx |
625 //allDocuments = allDocuments.add(this);
627 doc = this.prinitByIndex(idx);
628 if(doc.isNil,{^nil});
631 prinitByIndex { | idx |
632 ^this.subclassResponsibility(thisMethod)
635 //this is called from the menu: open, new
637 ^Document.implementationClass.prBasicNew.initLast
641 ^this.subclassResponsibility(thisMethod)
645 ^this.subclassResponsibility(thisMethod)
649 initFromPath { | path, selectionStart, selectionLength |
652 stpath = this.class.standardizePath(path);
653 this.propen(stpath, selectionStart, selectionLength);
655 this.class.allDocuments.do{ |d|
656 if(d.path == stpath.absolutePath){
662 this.background_(Color.white);
665 propen { | path, selectionStart=0, selectionLength=0 |
666 ^this.subclassResponsibility(thisMethod)
669 // private newTextWindow
670 initByString{ | argTitle, str, makeListener |
672 this.prinitByString(argTitle, str, makeListener);
673 this.background_(Color.white);
674 if(dataptr.isNil,{^nil});
676 this.title = argTitle;
679 prinitByString { | title, str, makeListener |
680 ^this.subclassResponsibility(thisMethod)
686 prSetBackgroundColor { | color |
687 ^this.subclassResponsibility(thisMethod)
689 prGetBackgroundColor { | color |
690 ^this.subclassResponsibility(thisMethod)
692 prSetSelectedBackgroundColor { | color |
693 ^this.subclassResponsibility(thisMethod);
695 prGetSelectedBackgroundColor { | color |
696 ^this.subclassResponsibility(thisMethod);
698 selectedRangeLocation {
699 ^this.subclassResponsibility(thisMethod)
702 ^this.subclassResponsibility(thisMethod)
705 prSelectLine { | line |
706 ^this.subclassResponsibility(thisMethod)
709 *prGetIndexOfListener {
710 ^this.subclassResponsibility(thisMethod)
714 //---not yet implemented
719 //*reviewUnsavedDocumentsWithAlertTitle
721 //*recentDocumentPaths
729 // Environment handling Document with its own envir must set and restore currentEnvironment on entry and exit.
730 // Requires alteration of *open, *new, closed, didBecomeKey, and didResignKey
734 if (this.class.current == this) {
735 if (savedEnvir.isNil) {
736 this.saveCurrentEnvironment
741 restoreCurrentEnvironment {
742 if (envir.notNil) { currentEnvironment = savedEnvir };
745 saveCurrentEnvironment {
747 savedEnvir = currentEnvironment;
748 currentEnvironment = envir;