diff --git a/sc/Document.sc b/sc/Document.sc
new file mode 100644
index 0000000..8e3d7b1
--- /dev/null
+++ b/sc/Document.sc
@@ -0,0 +1,587 @@
+// Since SC v3.2 dev, Document is an ABSTRACT class. Can't be instantiated directly.
+// Subclasses provide the editor-specific implementation, e.g. CocoaDocument for the standard Mac interface.
+// Subclasses also (in their SC code files) add a "implementationClass" method to Document to tell it to use them.
+
+Document {
+
+ classvar
current;
+ classvar initAction;
+
+ classvar <>autoRun = true;
+
+ classvar <>implementationClass;
+
+ //don't change the order of these vars:
+ var keyDownAction, <>keyUpAction, <>mouseUpAction;
+ var <>toFrontAction, <>endFrontAction, <>onClose, <>mouseDownAction;
+
+ var = 0 }
+ } { start = start - 1 };
+ while {
+ str[end] !== Char.nl and: { end < max }
+ } { end = end + 1 };
+ ^str.copyRange(start + 1, end);
+ }
+
+//actions:
+
+ didBecomeKey {
+ this.class.current = this;
+ this.saveCurrentEnvironment;
+ toFrontAction.value(this);
+ }
+
+ didResignKey {
+ endFrontAction.value(this);
+ this.restoreCurrentEnvironment;
+ }
+
+ mouseUp{ | x, y, modifiers, buttonNumber, clickCount, clickPos |
+ mouseUpAction.value(this, x, y, modifiers, buttonNumber, clickCount)
+ }
+
+ keyDown { | character, modifiers, unicode, keycode |
+ this.class.globalKeyDownAction.value(this,character, modifiers, unicode, keycode);
+ keyDownAction.value(this,character, modifiers, unicode, keycode);
+ }
+
+ keyUp { | character, modifiers, unicode, keycode |
+ this.class.globalKeyUpAction.value(this,character, modifiers, unicode, keycode);
+ keyUpAction.value(this,character, modifiers, unicode, keycode);
+ }
+
+ == { | doc |
+ ^if(this.path.isNil or: { doc.path.isNil }) { doc === this } {
+ this.path == doc.path
+ }
+ }
+
+ hash {
+ ^(this.path ? this).hash
+ }
+
+ *defaultUsesAutoInOutdent_ {|bool|
+ Document.implementationClass.prDefaultUsesAutoInOutdent_(bool)
+ }
+
+ usesAutoInOutdent_ {|bool|
+ this.prUsesAutoInOutdent_(bool)
+ }
+
+ *prDefaultUsesAutoInOutdent_{|bool|
+ this.subclassResponsibility(thisMethod);
+ }
+
+ *prPostColor_{ |color|
+ this.subclassResponsibility(thisMethod);
+ }
+
+ prUsesAutoInOutdent_{|bool|
+ ^this.subclassResponsibility(thisMethod);
+ }
+
+
+// private implementation
+
+ prIsEditable_{ | editable=true |
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prSetTitle { | argName |
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prGetTitle {
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prGetFileName {
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prSetFileName { | apath |
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prGetBounds { | argBounds |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ prSetBounds { | argBounds |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ text {
+ ^this.subclassResponsibility(thisMethod)
+ }
+ selectedText {
+ ^this.subclassResponsibility(thisMethod)
+ }
+ selectUnderlinedText { | clickPos |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ linkAtClickPos { | clickPos |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ rangeText { | rangestart=0, rangesize=1 |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ prclose {
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ closed {
+ onClose.value(this); // call user function
+ this.restoreCurrentEnvironment;
+ allDocuments.remove(this);
+ dataptr = nil;
+ }
+
+ prinsertText { | dataPtr, txt |
+ ^this.subclassResponsibility(thisMethod)
+ }
+ insertTextRange { | string, rangestart, rangesize |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ prAdd {
+ allDocuments = allDocuments.add(this);
+ this.editable = true;
+ if (autoRun) {
+ if (this.rangeText(0,7) == "/*RUN*/")
+ {
+ this.text.interpret;
+ }
+ };
+ current = this;
+ initAction.value(this);
+
+ }
+
+ //this is called after recompiling the lib
+ *prnumberOfOpen {
+ ^this.subclassResponsibility(thisMethod)
+ }
+ *numberOfOpen {
+ thisProcess.platform.when(\_NumberOfOpenTextWindows) {
+ ^this.prnumberOfOpen
+ } { ^allDocuments.size };
+ ^0
+ }
+
+ *newFromIndex { | idx |
+ ^super.new.initByIndex(idx)
+ }
+ initByIndex { | idx |
+ //allDocuments = allDocuments.add(this);
+ var doc;
+ doc = this.prinitByIndex(idx);
+ if(doc.isNil,{^nil});
+ this.prAdd;
+ }
+ prinitByIndex { | idx |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ //this is called from the menu: open, new
+ *prGetLast {
+ ^Document.implementationClass.prBasicNew.initLast
+ }
+
+ initLast {
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ prGetLastIndex {
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ // private open
+ initFromPath { | path, selectionStart, selectionLength |
+ var stpath;
+ // path = apath;
+ stpath = this.class.standardizePath(path);
+ this.propen(stpath, selectionStart, selectionLength);
+ if(dataptr.isNil,{
+ this.class.allDocuments.do{ |d|
+ if(d.path == stpath.absolutePath){
+ ^d
+ }
+ };
+ ^nil
+ });
+ this.background_(Color.white);
+ ^this.prAdd;
+ }
+ propen { | path, selectionStart=0, selectionLength=0 |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ // private newTextWindow
+ initByString{ | argTitle, str, makeListener |
+
+ this.prinitByString(argTitle, str, makeListener);
+ this.background_(Color.white);
+ if(dataptr.isNil,{^nil});
+ this.prAdd;
+ this.title = argTitle;
+
+ }
+ prinitByString { | title, str, makeListener |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ // other private
+ // if -1 whole doc
+
+ prSetBackgroundColor { | color |
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prGetBackgroundColor { | color |
+ ^this.subclassResponsibility(thisMethod)
+ }
+ prSetSelectedBackgroundColor { | color |
+ ^this.subclassResponsibility(thisMethod);
+ }
+ prGetSelectedBackgroundColor { | color |
+ ^this.subclassResponsibility(thisMethod);
+ }
+ selectedRangeLocation {
+ ^this.subclassResponsibility(thisMethod)
+ }
+ selectedRangeSize {
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ prSelectLine { | line |
+ ^this.subclassResponsibility(thisMethod)
+ }
+
+ *prGetIndexOfListener {
+ if (this.implementationClass.isNil) {
+ ^nil
+ };
+
+ if (this.implementationClass.respondsTo(\prGetIndexOfListener)) {
+ ^this.implementationClass.prGetIndexOfListener
+ } {
+ ^nil
+ }
+ }
+
+ //---not yet implemented
+ // ~/Documents
+ // /Volumes
+ // Music/Patches
+
+ //*reviewUnsavedDocumentsWithAlertTitle
+ //*saveAllDocuments
+ //*recentDocumentPaths
+ //save
+ //saveAs
+ //print
+ //
+ //hasPath was loaded
+
+
+// Environment handling Document with its own envir must set and restore currentEnvironment on entry and exit.
+// Requires alteration of *open, *new, closed, didBecomeKey, and didResignKey
+
+ envir_ { | ev |
+ envir = ev;
+ if (this.class.current == this) {
+ if(envir.isNil) {
+ this.restoreCurrentEnvironment
+ } {
+ if (savedEnvir.isNil) {
+ this.saveCurrentEnvironment
+ }
+ }
+ }
+ }
+
+ restoreCurrentEnvironment {
+ if (savedEnvir.notNil) {
+ currentEnvironment = savedEnvir;
+ savedEnvir = nil;
+ }
+ }
+
+ saveCurrentEnvironment {
+ if (envir.notNil) {
+ savedEnvir = currentEnvironment;
+ currentEnvironment = envir;
+ }
+ }
+
+ *prBasicNew {
+ ^super.new
+ }
+}