Syntax10.Scn.Fnt Syntax10i.Scn.Fnt StampElems Alloc 9 Apr 96 Syntax12i.Scn.Fnt FoldElems LineElems Alloc Syntax10m.Scn.Fnt Syntax10b.Scn.Fnt MODULE XE; (** SHML 10 Dec 90, (** eXtended Edit: Supports various enhancements over usual TextFrames.Handle for programmer's purposes *) (* Created by Stefan H._M. Ludwig, Institute for Computer Systems, ETH Zurich, ludwig@inf.ethz.ch, 10 Dec 90 Changes: 5 Jan 96: SHML - added cleanup task to fix a text after a trap occurred during compilation new: cleanup, PrepareText, RestoreText 8 Jan 96: SHML - fixed bug in RestoreText new: viewerComp 17 Jan 96: SHML - XE.Comp of a list of files will stop after first error, open the file, and mark the errors in it new: CheckErrors, OpenModViewer 31 Jan 96: SHML - selected text remains selected when an UpdateMsg with id Texts.replace is sent (e.g. change font no longer clears selection). new: keepSel in Handle 2 Feb 96: SHML - restructured configuration of menu frame, change command names changed: Err -> Error, Comp -> Compile new: DefMenu(Num), TextMenu(Num), ToolMenu(Num), WideMenu(Num), AsciiMenu(Num) 14 Feb 96: SHML - XE.Compile generates compiler command from file extension, InNameSet/TrackSelection accept "@" as part of a name 7 Mar 96: SHML - double left click clears selection. Useful for Edit.Search, when something is changed and the selection remains, but it's not the searchable selection. new: lastCarSet 11 Mar 96: SHML - added colon ":" as a valid filename content to the Amiga section 3 Apr 96: SHML - Uses module Host. Fully portable. removed: InFileNameSet, HostDependentStuff, OptionChar1, OptionChar2 (* Declarations *) IMPORT Host, Modules, Display, Input, Files, Fonts, Texts, Viewers, Oberon, TextFrames, MenuViewers, FoldElems; CONST GetHandlerKey* = -210566; (** secret number to get XE.Handle *) WordBoundary* = 0; NameBoundary* = 1; FileNameBoundary* = 2; (** type for WordBounds checking *) DefErrFile = "OberonErrors.Text"; ErrFont = "Syntax8.Scn.Fnt"; ML = 2; MM = 1; MR = 0; CtrlB = 2X; CtrlD = 4X; CtrlE = 5X; CtrlF = 6X; BS = 08X; LF = 0AX; CtrlK = 0BX; CR = 0DX; CtrlN = 0EX; CtrlP = 10X; CtrlT = 14X; CtrlW = 17X; CtrlX = 18X; CtrlZ = 1AX; UpArrow = 0C1X; DnArrow = 0C2X; MaxPat = 32; Version = "XE (SHML 9 Apr 96)"; XEMenu = "XE.Menu.Text"; EditMenu = "Edit.Menu.Text"; SystemMenu = "System.Menu.Text"; ConfigurationName = "XE.Configuration.Text"; KeyHandler = "EditKeys.GetKeyHandler"; DefComp = "Compiler.Compile"; (* default compiler command *) DefOpenCmd = "Doc.Open"; DefOpenCmd1 = "XE.Open"; (* commands used by OpenCall *) Empty0 = "Empty.Mod"; Empty1 = "Empty.Tool"; Empty3 = "Empty.c"; (* default empty files for Defaults *) Ext00 = "Mod"; Ext01 = "Text"; Ext1 = "Tool"; Ext30 = "c"; Ext31 = "h"; (* default file extensions for Defaults *) AsciiFont = "Courier10.Scn.Fnt"; (* used by OpenAscii for displaying ascii texts *) DefMenuNum = 0; TextMenuNum = 1; ToolMenuNum = 2; WideMenuNum = 3; AsciiMenuNum = 4; DefMenu = "System.Close System.Copy System.Grow Edit.Search Edit.Replace All Edit.Parcs XE.Compile XE.Error Edit.Store "; TextMenu = "System.Close System.Copy System.Grow Edit.Search Edit.Replace All Edit.Parcs Edit.Store "; ToolMenu = "System.Close System.Grow Edit.Parcs Edit.Store "; WideMenu = "System.Close System.Copy System.Grow Edit.Search Edit.Replace All Edit.Parcs XE.Compile XE.Error Edit.Store | Log.Open | XE.SysOpen Strip "; AsciiMenu = "System.Close System.Copy System.Grow Edit.Search Edit.Replace All XE.Compile EditTools.LocateLine ^ EdiT.StoreAscii "; TYPE LongName = ARRAY 128 OF CHAR; Name = ARRAY 32 OF CHAR; Elem = POINTER TO ElemDesc; ElemDesc = RECORD (Texts.ElemDesc) err: INTEGER; pos: LONGINT; wide: BOOLEAN; num: ARRAY 8 OF CHAR; msg: LongName END; Element = POINTER TO ElementDesc; ElementDesc = RECORD compiler, ext: Name; errFile: LongName; next: Element END; wr: Texts.Writer; errT: Texts.Text; errFnt: Fonts.Font; keyHandle: Display.Handler; cleanup: RECORD (* exception handling, if traps occur during compilation *) text: Texts.Text; oldNotify: Texts.Notifier; task: Oberon.Task END; compiler, defComp, openCmd: Name; empty: ARRAY 4 OF Name; ext: ARRAY 4, 2 OF Name; first, viewerComp: BOOLEAN; delay, lastCarSet: LONGINT; root: Element; find: RECORD len: SHORTINT; buf: ARRAY MaxPat OF CHAR; shiftTab: ARRAY 256 OF SHORTINT END; (* Support *) PROCEDURE Str(s: ARRAY OF CHAR); BEGIN Texts.WriteString(wr, s) END Str; PROCEDURE Ch(ch: CHAR); BEGIN Texts.Write(wr, ch) END Ch; PROCEDURE Ln; BEGIN Texts.WriteLn(wr); Texts.Append(Oberon.Log, wr.buf) END Ln; PROCEDURE Extension(name: ARRAY OF CHAR; VAR ext: ARRAY OF CHAR); (* get extension of name *) VAR i, j: INTEGER; BEGIN i := -1; REPEAT INC(i) UNTIL name[i] = 0X; REPEAT DEC(i) UNTIL (name[i] = ".") OR (i = 0); IF i = 0 THEN ext[0] := 0X ELSE j := -1; REPEAT INC(i); INC(j); ext[j] := name[i] UNTIL name[i] = 0X END END Extension; PROCEDURE Append(src: ARRAY OF CHAR; VAR dest: ARRAY OF CHAR); (* append src to dest if no "." in src *) VAR i, off: INTEGER; BEGIN off := -1; REPEAT INC(off) UNTIL (dest[off] = 0X) OR (dest[off] = "."); IF dest[off] # "." THEN i := -1; REPEAT INC(i); dest[i+off] := src[i] UNTIL src[i] = 0X END END Append; PROCEDURE SearchPair(ext: ARRAY OF CHAR; VAR prev: Element): Element; VAR l: Element; BEGIN l := root; prev := NIL; WHILE (l # NIL) & (l.ext # ext) DO prev := l; l := l.next END; RETURN l END SearchPair; PROCEDURE ScanFirst(VAR s: Texts.Scanner); (* Scan first parameter *) VAR sel: Texts.Text; beg, end, time: LONGINT; BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s); IF (s.class = Texts.Char) & (s.line = 0) & (s.c = "^") THEN Oberon.GetSelection(sel, beg, end, time); IF time >= 0 THEN Texts.OpenScanner(s, sel, beg); Texts.Scan(s) END END END ScanFirst; PROCEDURE InstallKeyHandler; VAR save, par: Oberon.ParList; res: INTEGER; BEGIN save := Oberon.Par; NEW(par); NEW(par.frame); par.frame.X := 0; par.frame.Y := 0; par.pos := -42; (* magic *) Oberon.Call(KeyHandler, par, FALSE, res); IF res = 0 THEN keyHandle := Oberon.Par.frame.handle ELSE keyHandle := NIL END; Oberon.Par := save; Modules.res := 0 (* bug in Modules? *) END InstallKeyHandler; PROCEDURE MenuFrame*(name, menu: ARRAY OF CHAR; line: INTEGER): TextFrames.Frame; (** open XEMenu/EditMenu/SystemMenu and if existant get lineth textline (counting starts with 0) as menuline; (line >= 0, 100) *) VAR mf: TextFrames.Frame; buf: Texts.Buffer; t: Texts.Text; r: Texts.Reader; start, end: LONGINT; ch: CHAR; menuFile: LongName; BEGIN ASSERT(line >= 0, 100); IF Files.Old(XEMenu) # NIL THEN menuFile := XEMenu ELSIF (line = 1) & (Files.Old(SystemMenu) # NIL) THEN menuFile := SystemMenu ELSIF Files.Old(EditMenu) # NIL THEN menuFile := EditMenu ELSE RETURN TextFrames.NewMenu(name, menu) END; NEW(t); Texts.Open(t, menuFile); Texts.OpenReader(r, t, 0); REPEAT (* skip line lines *) start := Texts.Pos(r); REPEAT Texts.Read(r, ch) UNTIL r.eot OR (ch = 0DX); DEC(line) UNTIL line = -1; IF r.eot THEN end := t.len ELSE end := Texts.Pos(r)-1 END; IF start = end THEN RETURN TextFrames.NewMenu(name, menu) ELSE NEW(buf); Texts.OpenBuf(buf); Texts.Save(t, start, end, buf); mf := TextFrames.NewMenu(name, ""); Texts.Append(mf.text, buf); RETURN mf END END MenuFrame; PROCEDURE OpenText(VAR t: Texts.Text; VAR name: ARRAY OF CHAR; scanName: ARRAY OF CHAR; scanClass: INTEGER; default, ext1, ext2: ARRAY OF CHAR); VAR extName: LongName; i, len: INTEGER; PROCEDURE Extend(VAR str: ARRAY OF CHAR; with: ARRAY OF CHAR); (* extend str with with *) VAR ls, le: INTEGER; BEGIN ls := -1; REPEAT INC(ls) UNTIL str[ls] = 0X; le := -1; REPEAT INC(le) UNTIL with[le] = 0X; IF ls <= LEN(str)-(le+1) THEN INC(ls, le+1); REPEAT str[ls] := with[le]; DEC(ls); DEC(le) UNTIL le = -1; str[ls] := "." END END Extend; PROCEDURE Try(): BOOLEAN; (* try opening name with ext1 or ext2 appended to it *) BEGIN COPY(name, extName); Extend(extName, ext1); t := TextFrames.Text(extName); IF t.len = 0 THEN COPY(name, extName); Extend(extName, ext2); t := TextFrames.Text(extName) END; RETURN t.len > 0 END Try; BEGIN IF first THEN first := FALSE; Str(Version); Ln; InstallKeyHandler END; (* write a startup message to the Log (once) *) find.len := 0; IF scanClass = Texts.String THEN t := TextFrames.Text(scanName); name[0] := '"'; i := 0; REPEAT INC(i); name[i] := scanName[i-1] UNTIL name[i] = 0X; name[i] := '"'; name[i+1] := 0X ELSIF scanClass # Texts.Name THEN t := TextFrames.Text(default); COPY(default, name) ELSE COPY(scanName, name); t := TextFrames.Text(name); (* use original name *) IF t.len = 0 THEN (* name doesn't exist *) IF Try() THEN COPY(extName, name) (* use extended name *) ELSE len := -1; REPEAT INC(len) UNTIL scanName[len] = 0X; REPEAT DEC(len) UNTIL (name[len] = ".") OR (len = 0); IF len # 0 THEN (* name[len] = "." *) i := -1; (* copy appended name to pattern for Edit.Show *) REPEAT INC(i); find.buf[i] := name[i+len+1] UNTIL find.buf[i] = 0X; find.len := SHORT(i); name[len] := 0X; (* delete extension, try with trimmed name *) IF Try() THEN COPY(extName, name) (* use extended name *) ELSE COPY(scanName, name) (* use original name with empty text *) END END END END END END OpenText; PROCEDURE Show(f: TextFrames.Frame; pos: LONGINT); VAR end, delta: LONGINT; BEGIN delta := 200; end := TextFrames.Pos(f, f.X+f.W, f.Y); WHILE ((f.org > pos) OR (pos >= end)) & (f.org # end) DO TextFrames.Show(f, pos-delta); DEC(delta, 20); end := TextFrames.Pos(f, f.X+f.W, f.Y) END END Show; PROCEDURE GetOptions(VAR s: Texts.Scanner; VAR options: ARRAY OF CHAR); VAR pos: LONGINT; i: INTEGER; ch: CHAR; r: Texts.Reader; BEGIN IF (s.class # Texts.Char) OR (s.c # Host.OptionChar) THEN options[0] := 0X ELSE pos := Texts.Pos(s); options[0] := s.c; ch := s.nextCh; i := 1; r := s; WHILE ((ch >= "0") & (ch <= "9") OR (ch >= "a") & (ch <= "z")) & (i < LEN(options)-1) DO options[i] := ch; INC(i); Texts.Read(r, ch) END; options[i] := 0X; pos := pos+(i-1); WHILE Texts.Pos(s) < pos DO Texts.Scan(s) END; Texts.Scan(s) END END GetOptions; PROCEDURE NoNotify(t: Texts.Text; op: INTEGER; beg, end: LONGINT); END NoNotify; PROCEDURE PrepareText(t: Texts.Text); BEGIN cleanup.text := t; cleanup.oldNotify := t.notify; t.notify := NoNotify; FoldElems.ExpandAll(t, 0, TRUE); Oberon.Install(cleanup.task) END PrepareText; PROCEDURE RestoreText; BEGIN Oberon.Remove(cleanup.task); FoldElems.CollapseAll(cleanup.text, {FoldElems.tempLeft}); cleanup.text.notify := cleanup.oldNotify; IF ~viewerComp THEN cleanup.text.notify(cleanup.text, Texts.replace, 0, cleanup.text.len) END END RestoreText; PROCEDURE BackRead*(VAR r(*inout*): Texts.Reader; t: Texts.Text; VAR ch(*out*): CHAR); VAR p: LONGINT; BEGIN p := Texts.Pos(r); IF p > 0 THEN Texts.OpenReader(r, t, p-1); Texts.Read(r, ch); Texts.OpenReader(r, t, p-1) ELSE ch := 0X END END BackRead; PROCEDURE InWordSet(ch: CHAR): BOOLEAN; BEGIN RETURN (("0" <= ch) & (ch <= "9") OR ("A" <= ch) & (ch <= "Z") OR ("a" <= ch) & (ch <= "z") OR (80X <= ch) & (ch <= 95X)) END InWordSet; PROCEDURE InNameSet(ch: CHAR): BOOLEAN; BEGIN RETURN InWordSet(ch) OR (ch = ".") OR (ch = "@") END InNameSet; PROCEDURE PrepareFind; (* prepare find.shiftTab according to current pattern *) VAR i: INTEGER; m: SHORTINT; BEGIN m := find.len+1; FOR i := 0 TO LEN(find.shiftTab)-1 DO find.shiftTab[i] := m END; (* init all chars with length of pattern+1 *) DEC(m); FOR i := 0 TO find.len-1 DO find.shiftTab[ORD(find.buf[i])] := m; DEC(m) END END PrepareFind; PROCEDURE Find(t: Texts.Text; beg: LONGINT; VAR end: LONGINT); (*<< Quicksearch algorithm, D.M. Sunday *) VAR r: Texts.Reader; i: INTEGER; found: BOOLEAN; ch: CHAR; BEGIN found := FALSE; REPEAT Texts.OpenReader(r, t, beg); Texts.Read(r, ch); i := 0; WHILE ~r.eot & (i < find.len) & (ch = find.buf[i]) DO Texts.Read(r, ch); INC(i) END; IF ~r.eot & (i < find.len) THEN Texts.OpenReader(r, t, beg+find.len); Texts.Read(r, ch); beg := beg+find.shiftTab[ORD(ch)] ELSE found := TRUE END UNTIL found; IF i = find.len THEN end := beg+find.len ELSE end := -1 END END Find; PROCEDURE SearchIdent(f: TextFrames.Frame); VAR t: Texts.Text; pos, dec, start: LONGINT; r: Texts.Reader; ch: CHAR; BEGIN t := f.text; IF find.len > 0 THEN (* simulate Edit.Show *) dec := 1; find.buf[find.len] := "*"; INC(find.len); find.buf[find.len] := 0X; (* search for name* *) PrepareFind; Find(t, 0, pos); IF pos = -1 THEN (* not found *) find.buf[find.len-1] := "-"; PrepareFind; Find(t, 0, pos); (* search for name- *) IF pos = -1 THEN (* not found *) DEC(find.len); find.buf[find.len] := 0X; start := 0; dec := 0; (* search for name *) PrepareFind; REPEAT Find(t, start, pos); IF pos > 0 THEN (* something found, check if it's an identifier. If not, search again *) Texts.OpenReader(r, t, pos); Texts.Read(r, ch); IF ("0" <= ch) & (ch <= "9") OR ("A" <= ch) & (ch <= "Z") OR ("a" <= ch) & (ch <= "z") THEN start := pos ELSE start := -1 END ELSE start := -1 END UNTIL start = -1; (*DEC(find.len); find.buf[find.len] := 0X; Find(t, 0, pos); dec := 0*) END END; IF pos > 0 THEN pos := pos-find.len; TextFrames.Show(f, pos); TextFrames.SetSelection(f, pos, pos+find.len-dec); Oberon.PassFocus(Viewers.This(f.X, f.Y)); TextFrames.SetCaret(f, pos+find.len-dec) END END END SearchIdent; (* Text Support *) PROCEDURE WordBounds*(t: Texts.Text; VAR beg(*inout*), end(*out*): LONGINT; type: INTEGER); (** locate the word bounds [beg, end[ in text t starting at beg; (type IN {WordBoundary, NameBoundary, FileNameBoundary}, 100) *) VAR r: Texts.Reader; ch: CHAR; word, name, fileName: BOOLEAN; BEGIN ASSERT(type IN {WordBoundary, NameBoundary, FileNameBoundary}, 100); word := type = WordBoundary; name := type = NameBoundary; fileName := type = FileNameBoundary; Texts.OpenReader(r, t, beg); REPEAT Texts.Read(r, ch) UNTIL r.eot OR word & ~InWordSet(ch) OR name & ~InNameSet(ch) OR fileName & ~Host.IsFileNameChar(ch); IF r.eot THEN end := t.len ELSE end := Texts.Pos(r)-1 END; Texts.OpenReader(r, t, beg); REPEAT BackRead(r, t, ch) UNTIL word & ~InWordSet(ch) OR name & ~InNameSet(ch) OR fileName & ~Host.IsFileNameChar(ch); IF ch = 0X THEN beg := 0 ELSE beg := Texts.Pos(r)+1 END END WordBounds; PROCEDURE EndOfLine*(f: TextFrames.Frame; VAR loc(*inout*): TextFrames.Location; org: LONGINT; VAR end(*inout*): LONGINT); (** locate end of line starting at loc and end *) BEGIN WHILE (end < f.text.len) & (loc.org <= org) DO INC(end, 30); TextFrames.LocatePos(f, end, loc) END; IF (end >= f.text.len) & (loc.org <= org) THEN end := f.text.len ELSE WHILE loc.org > org DO end := loc.org; TextFrames.LocatePos(f, end-1, loc) END END END EndOfLine; PROCEDURE TrackSelection*(f: TextFrames.Frame; VAR x(*inout*), y(*inout*): INTEGER; VAR keySum(*inout*): SET); VAR keys: SET; beg, end, begW, endW, begN, endN, pos: LONGINT; loc, loc1: TextFrames.Location; v: Viewers.Viewer; upper: TextFrames.Frame; r: Texts.Reader; ch: CHAR; BEGIN v := Viewers.This(f.X, f.Y); v := v.next(Viewers.Viewer); IF (v.dsc # NIL) & (v.dsc.next # NIL) & (v.dsc.next IS TextFrames.Frame) THEN upper := v.dsc.next(TextFrames.Frame); IF upper.hasSel & (upper.text = f.text) THEN TextFrames.LocateLine(upper, upper.bot, loc); IF (upper.selbeg.pos < loc.org) & (upper.org < upper.selend.pos) & (upper.selbeg.pos <= TextFrames.Pos(f, x, y)) THEN TextFrames.SetSelection(f, upper.selbeg.pos, TextFrames.Pos(f, x, y)+1) ELSE TextFrames.RemoveSelection(upper); upper := NIL END ELSE upper := NIL END ELSE upper := NIL END; IF upper = NIL THEN pos := TextFrames.Pos(f, x, y); IF f.hasSel & (Oberon.Time() < f.time+delay) THEN beg := f.selbeg.pos; end := f.selend.pos; IF (beg+1 = end) & (pos = beg) THEN (* one char selected, mouse on same character *) TextFrames.LocatePos(f, beg, loc); TextFrames.LocatePos(f, end, loc1); Texts.OpenReader(r, f.text, beg); Texts.Read(r, ch); IF (end = f.text.len) OR (loc.org # loc1.org) OR ~InNameSet(ch) THEN (* extend to whole line *) EndOfLine(f, loc1, loc.org, end); TextFrames.SetSelection(f, loc.org, end) ELSE (* (end # f.text.len) & (loc.org = loc1.org) & InNameSet(ch) *) begW := pos; endW := pos+1; IF (ch = ".") OR (ch = "@") THEN WordBounds(f.text, begW, endW, NameBoundary) ELSE WordBounds(f.text, begW, endW, WordBoundary) END; begN := pos; endN := pos+1; WordBounds(f.text, begN, endN, NameBoundary); IF (begW = beg) & (endW = end) THEN IF (begN = beg) & (endN = end) THEN (* single char InNameSet -> select line *) EndOfLine(f, loc1, loc.org, end); TextFrames.SetSelection(f, loc.org, end) ELSE TextFrames.SetSelection(f, begN, endN) (* name *) END ELSE TextFrames.SetSelection(f, begW, endW) (* word *) END END ELSIF (beg <= pos) & (pos < end) THEN (* mouse within selection *) TextFrames.LocatePos(f, beg, loc); TextFrames.LocatePos(f, end-1, loc1); IF loc.org = loc1.org THEN (* selection is at most one line *) begW := pos; endW := pos+1; WordBounds(f.text, begW, endW, WordBoundary); begN := pos; endN := pos+1; WordBounds(f.text, begN, endN, NameBoundary); IF (begW = beg) & (endW = end) & ((begN < beg) OR (end < endN)) THEN (* word selected -> extend to name *) TextFrames.SetSelection(f, begN, endN) ELSE (* name selected -> extend to line *) endN := loc1.pos; EndOfLine(f, loc1, loc.org, endN); IF (loc.org # beg) OR (endN # end) THEN TextFrames.SetSelection(f, loc.org, endN) ELSE TextFrames.SetSelection(f, pos, pos+1) (* select single char *) END END ELSE TextFrames.SetSelection(f, pos, pos+1) (* not same line -> select single char *) END ELSE TextFrames.SetSelection(f, pos, pos+1) (* not within selection -> select single char *) END ELSE TextFrames.SetSelection(f, pos, pos+1) (* no selection or time out -> select single char *) END; (* f.hasSel & ... *) end := f.selend.pos ELSE end := upper.selbeg.pos END; (* upper = NIL *) REPEAT Input.Mouse(keys, x, y); keySum := keySum+keys; Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y); pos := TextFrames.Pos(f, x, y)+1; IF f.hasSel THEN IF pos >= end THEN TextFrames.SetSelection(f, f.selbeg.pos, pos); IF upper # NIL THEN TextFrames.SetSelection(upper, upper.selbeg.pos, pos); upper.selend.pos := f.selend.pos END END ELSE TextFrames.SetSelection(f, TextFrames.Pos(f, x, y), TextFrames.Pos(f, x, y)+1) END UNTIL keys = {}; IF upper # NIL THEN f.selbeg.pos := upper.selbeg.pos END END TrackSelection; PROCEDURE CaretVisible(f: TextFrames.Frame; pos: LONGINT): BOOLEAN; BEGIN RETURN f.hasCar & (f.carloc.y >= f.bot) & (f.carloc.pos = pos) END CaretVisible; PROCEDURE MoveTextStretch(from: Texts.Text; to: TextFrames.Frame; beg, end, pos: LONGINT); VAR len: LONGINT; BEGIN (* only if other text or target pos not within selection *) IF ((from # to.text) OR (pos < beg) OR (end < pos)) THEN len := end-beg; IF (from = to.text) & (end < pos) THEN DEC(pos, len) END; (* dec caret pos by length of sel *) Texts.Save(from, beg, end, wr.buf); Texts.Delete(from, beg, end); Texts.Insert(to.text, pos, wr.buf); TextFrames.SetCaret(to, pos+len); IF CaretVisible(to, pos+len) THEN TextFrames.SetSelection(to, pos, pos+len) END END END MoveTextStretch; PROCEDURE MoveSelection(f: TextFrames.Frame; x, y: INTEGER; keySum: SET; VAR done: BOOLEAN); VAR keys: SET; v: Viewers.Viewer; target: TextFrames.Frame; time: LONGINT; oldX, oldY: INTEGER; BEGIN time := Oberon.Time(); oldX := x; oldY := y; REPEAT Input.Mouse(keys, x, y); keySum := keySum+keys; Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y) UNTIL (keys = {}) OR (keySum # {MM}) OR (ABS(oldX-x) < 3) & (ABS(oldY-y) < 3) & (Oberon.Time() > time+delay DIV 2); IF (keys # {}) & (keySum = {MM, ML}) THEN v := Viewers.This(x, y); IF (v.dsc # NIL) & (v.dsc.next # NIL) & (v.dsc.next IS TextFrames.Frame) THEN target := v.dsc.next(TextFrames.Frame); Oberon.PassFocus(v); TextFrames.TrackCaret(target, x, y, keySum); IF keySum = {MM, ML} THEN MoveTextStretch(f.text, target, f.selbeg.pos, f.selend.pos, target.carloc.pos) END END; done := TRUE ELSE done := FALSE END END MoveSelection; PROCEDURE OpenCall(f: TextFrames.Frame; x, y: INTEGER; pos: LONGINT); VAR s: Texts.Scanner; par: Oberon.ParList; loc: TextFrames.Location; beg, end, newPos: LONGINT; res: INTEGER; BEGIN TextFrames.LocateChar(f, x, y, loc); newPos := loc.pos; REPEAT beg := newPos; WordBounds(f.text, beg, end, FileNameBoundary); DEC(newPos) UNTIL (beg < end) OR (newPos < pos); IF beg < end THEN Texts.OpenScanner(s, f.text, beg); Texts.Scan(s); IF (s.line = 0) & (s.class = Texts.Name) THEN NEW(par); par.frame := f; par.text := f.text; par.pos := beg; Oberon.Call(openCmd, par, FALSE, res); IF res # 0 THEN Oberon.Call(DefOpenCmd1, par, FALSE, res) END END END END OpenCall; (** Error Element *) PROCEDURE ElemWidth(e: Elem): INTEGER; VAR pat: Display.Pattern; i, px, dx, x, y, w, h: INTEGER; str: LongName; BEGIN i := 0; px := 0; IF e.wide THEN COPY(e.msg, str) ELSE COPY(e.num, str) END; WHILE str[i] # 0X DO Display.GetChar(errFnt.raster, str[i], dx, x, y, w, h, pat); INC(px, dx); INC(i) END; RETURN px+6 END ElemWidth; PROCEDURE UpdateErr(e: Elem); VAR t: Texts.Text; BEGIN (* precondition: e.pos is correct *) t := Texts.ElemBase(e); t.notify(t, Texts.replace, e.pos, e.pos+1) END UpdateErr; PROCEDURE ShowErrMsg(e: Elem; col: SHORTINT; x0, y0, dw: INTEGER); VAR pat: Display.Pattern; i, px, rm, dx, x, y, w, h: INTEGER; ch: CHAR; str: LongName; BEGIN IF e.wide THEN COPY(e.msg, str) ELSE COPY(e.num, str) END; i := 0; px := x0+3; rm := x0+dw-3; INC(y0, 2); LOOP ch := str[i]; INC(i); IF ch = 0X THEN EXIT END; Display.GetChar(errFnt.raster, ch, dx, x, y, w, h, pat); IF px+dx > rm THEN EXIT END; Display.CopyPattern(col, pat, px+x, y0+y, Display.invert); INC(px, dx) END END ShowErrMsg; PROCEDURE DeleteErrElems*(t: Texts.Text); VAR r: Texts.Reader; pos: LONGINT; BEGIN Texts.OpenReader(r, t, 0); Texts.ReadElem(r); WHILE r.elem # NIL DO IF r.elem IS Elem THEN pos := Texts.Pos(r); Texts.Delete(t, pos-1, pos); Texts.OpenReader(r, t, pos) END; Texts.ReadElem(r) END END DeleteErrElems; PROCEDURE ElemHandle(e: Texts.Elem; VAR msg: Texts.ElemMsg); VAR copy: Elem; w, h: INTEGER; keys, keySum: SET; PROCEDURE Expand(el: Elem); VAR s: Texts.Scanner; n: INTEGER; ch: CHAR; BEGIN IF el.msg[0] = 0X THEN Texts.OpenScanner(s, errT, 0); REPEAT s.line := 0; REPEAT Texts.Scan(s) UNTIL s.eot OR (s.line # 0) UNTIL s.eot OR (s.class = Texts.Int) & (s.i = el.err); IF ~s.eot THEN Texts.Read(s, ch); n := 0; WHILE ~s.eot & (ch # CR) & (n+1 < LEN(el.msg)) DO el.msg[n] := ch; INC(n); Texts.Read(s, ch) END; el.msg[n] := 0X ELSE el.msg := "no message found" END END; el.wide := TRUE; el.W := LONG(ElemWidth(el))*TextFrames.Unit END Expand; BEGIN WITH e: Elem DO WITH msg: TextFrames.DisplayMsg DO IF ~msg.prepare THEN w := SHORT(e.W DIV TextFrames.Unit); h := SHORT(e.H DIV TextFrames.Unit); Display.ReplConst(Display.white, msg.X0+1, msg.Y0+2, w-2, h, Display.replace); ShowErrMsg(e, msg.col, msg.X0, msg.Y0+2, w) END | msg: TextFrames.TrackMsg DO (* a mouse click hit the element *) IF msg.keys = {MM} THEN w := SHORT(e.W DIV TextFrames.Unit); h := SHORT(e.H DIV TextFrames.Unit); Oberon.RemoveMarks(msg.X0, msg.Y0, w, h); Display.ReplConst(Display.white, msg.X0+2, msg.Y0+3, w-4, h-2, Display.invert); keySum := msg.keys; REPEAT Input.Mouse(keys, msg.X, msg.Y); keySum := keySum+keys; Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, msg.X, msg.Y) UNTIL keys = {}; Display.ReplConst(Display.white, msg.X0+2, msg.Y0+3, w-4, h-2, Display.invert); e.pos := msg.pos; IF keySum = {MM} THEN (* expand/reduce element *) IF ~e.wide THEN Expand(e) ELSE e.wide := FALSE; e.W := LONG(ElemWidth(e))*TextFrames.Unit END; UpdateErr(e) END; msg.keys := {} END | msg: Texts.CopyMsg DO (* copy element *) NEW(copy); Texts.CopyElem(e, copy); copy.err := e.err; copy.pos := e.pos; copy.wide := e.wide; copy.num := e.num; copy.msg := e.msg; msg.e := copy ELSE END END END ElemHandle; PROCEDURE InsertErrAt*(t: Texts.Text; pos: LONGINT; err: INTEGER); (** insert an error element into text t at position pos marking error err *) VAR e: Elem; h: ARRAY 8 OF CHAR; j, k: INTEGER; BEGIN NEW(e); e.H := 3*TextFrames.mm; e.handle := ElemHandle; e.err := err; e.msg := ""; e.wide := FALSE; k := 0; REPEAT h[k] := CHR(err MOD 10 + ORD("0")); err := err DIV 10; INC(k) UNTIL err = 0; j := 0; REPEAT DEC(k); e.num[j] := h[k]; INC(j) UNTIL k = 0; e.num[j] := 0X; e.W := LONG(ElemWidth(e))*TextFrames.Unit; Texts.WriteElem(wr, e); Texts.Insert(t, pos, wr.buf) END InsertErrAt; PROCEDURE CheckErrors*(VAR s(*out*): Texts.Scanner; logPos: LONGINT): BOOLEAN; (** are there error messages in the log text starting at logPos ? *) BEGIN Texts.OpenScanner(s, Oberon.Log, logPos); REPEAT Texts.Scan(s) UNTIL s.eot OR (s.class = Texts.Name) & (s.s = "pos"); RETURN (s.class = Texts.Name) & (s.s = "pos") END CheckErrors; PROCEDURE MarkErrors*(f: TextFrames.Frame; logPos: LONGINT); (** mark errors in frame f starting in log text at logPos ? *) VAR s: Texts.Scanner; pos, delta: LONGINT; err: INTEGER; error: BOOLEAN; BEGIN DeleteErrElems(f.text); IF CheckErrors(s, logPos) THEN delta := 0; LOOP s.line := 0; REPEAT Texts.Scan(s) UNTIL s.eot OR (s.line # 0) OR (s.class = Texts.Int); IF s.eot OR (s.line # 0) THEN EXIT END; pos := s.i; Texts.Scan(s); error := (s.s = "err") OR (s.s = "pc"); REPEAT Texts.Scan(s) UNTIL s.eot OR (s.line # 0) OR (s.class = Texts.Int); IF s.eot OR (s.line # 0) THEN EXIT END; err := SHORT(s.i); (* display errors, but warnings only if it's the Analyzer *) IF error OR (compiler = "Analyzer.Analyze") THEN InsertErrAt(f.text, pos+delta, err); INC(delta) END; REPEAT Texts.Scan(s) UNTIL s.eot OR (s.line # 0) END END END MarkErrors; PROCEDURE ErrCheck*(e: Texts.Elem): BOOLEAN; BEGIN RETURN e IS Elem END ErrCheck; (** Handler *) PROCEDURE HandleCall(f: TextFrames.Frame; pos: LONGINT; new: BOOLEAN); VAR s: Texts.Scanner; par: Oberon.ParList; res, i, j: INTEGER; msg: ARRAY 128 OF CHAR; BEGIN Texts.OpenScanner(s, f.text, pos); Texts.Scan(s); IF (s.class = Texts.Name) & (s.line = 0) THEN i := -1; REPEAT INC(i) UNTIL (i = s.len) OR (s.s[i] = "."); j := i; REPEAT INC(j) UNTIL (j >= s.len) OR (s.s[j] = "."); IF (j >= s.len) & (s.s[i] = ".") THEN NEW(par); par.frame := f; par.text := f.text; par.pos := pos+s.len; Oberon.Call(s.s, par, new, res); IF res # 0 THEN Host.CallError(s.s, res, msg); Str(msg); Ln END END END END HandleCall; PROCEDURE Handle*(f: Display.Frame; VAR msg: Display.FrameMsg); VAR tf, ff: TextFrames.Frame; t, sel: Texts.Text; loc: TextFrames.Location; copyOver: Oberon.CopyOverMsg; r: Texts.Reader; handled, done, keepSel: BOOLEAN; ch: CHAR; x, y: INTEGER; pos, beg, end, len, time: LONGINT; keySum: SET; PROCEDURE PartialFolds(text: Texts.Text; b, e: LONGINT): BOOLEAN; CONST leftMode = {FoldElems.colLeft, FoldElems.expLeft, FoldElems.tempLeft, FoldElems.findLeft}; VAR level: INTEGER; BEGIN level := 0; Texts.OpenReader(r, text, b); Texts.ReadElem(r); WHILE (r.elem # NIL) & (Texts.Pos(r) <= e) DO IF r.elem IS FoldElems.Elem THEN IF r.elem(FoldElems.Elem).mode IN leftMode THEN INC(level) ELSE DEC(level) END END; Texts.ReadElem(r) END; RETURN level # 0 END PartialFolds; PROCEDURE ThisSubFrame(x, y: INTEGER): Display.Frame; VAR sf: Display.Frame; BEGIN sf := f.dsc; WHILE (sf # NIL) & ((x < sf.X) OR (x >= sf.X+sf.W) OR (y < sf.Y) OR (y >= sf.Y+sf.H)) DO sf := sf.next END; RETURN sf END ThisSubFrame; BEGIN tf := f(TextFrames.Frame); IF keyHandle # NIL THEN keyHandle(tf, msg) END; t := tf.text; handled := TRUE; WITH msg: Oberon.InputMsg DO IF (msg.id = Oberon.track) & (msg.X >= tf.X+tf.barW) & (ThisSubFrame(msg.X, msg.Y) = NIL) THEN IF ML IN msg.keys THEN Oberon.PassFocus(Viewers.This(tf.X, tf.Y)); TextFrames.TrackCaret(tf, x, y, keySum); IF (keySum = {ML, MM}) & tf.hasCar THEN Oberon.GetSelection(sel, beg, end, time); IF time >= 0 THEN Texts.Save(sel, beg, end, wr.buf); len := end-beg; pos := tf.carloc.pos; Texts.Insert(tf.text, pos, wr.buf); TextFrames.SetCaret(tf, pos+len); IF CaretVisible(tf, pos+len) THEN TextFrames.SetSelection(tf, pos, pos+len) END END ELSIF (keySum = {ML, MR}) & tf.hasCar & (tf.carloc.pos < tf.text.len) THEN Oberon.GetSelection(sel, beg, end, time); IF time >= 0 THEN Texts.OpenReader(r, tf.text, tf.carloc.pos); Texts.Read(r, ch); Texts.ChangeLooks(sel, beg, end, {0, 1, 2}, r.fnt, r.col, r.voff) END ELSIF (keySum = {ML}) & tf.hasCar THEN (* clear selection with left double click *) IF Oberon.Time() < lastCarSet+delay THEN TextFrames.RemoveSelection(tf) ELSE lastCarSet := Oberon.Time() END END ELSIF MM IN msg.keys THEN x := msg.X; y := msg.Y; pos := TextFrames.Pos(tf, x, y); IF tf.hasSel & (tf.selbeg.pos <= pos) & (pos < tf.selend.pos) THEN MoveSelection(tf, x, y, msg.keys, done) ELSE done := FALSE END; IF ~done THEN Texts.OpenReader(r, t, pos); Texts.ReadElem(r); IF (r.elem = NIL) OR (Texts.Pos(r) # pos+1) THEN (* no elem at this position *) TextFrames.TrackWord(tf, x, y, pos, keySum); keySum := keySum+msg.keys; IF (keySum = {MM}) OR (keySum = {MM, ML}) THEN HandleCall(tf, pos, keySum = {MM, ML}) ELSIF keySum = {MM, MR} THEN OpenCall(tf, x, y, pos) END ELSE handled := FALSE END END ELSIF MR IN msg.keys THEN TrackSelection(tf, msg.X, msg.Y, msg.keys); IF (msg.keys = {MM, MR}) & tf.hasSel & ~PartialFolds(tf.text, tf.selbeg.pos, tf.selend.pos) THEN copyOver.text := tf.text; copyOver.beg := tf.selbeg.pos; copyOver.end := tf.selend.pos; len := copyOver.end-copyOver.beg; IF (Oberon.FocusViewer IS MenuViewers.Viewer) & (Oberon.FocusViewer.dsc.next # NIL) & (Oberon.FocusViewer(MenuViewers.Viewer).dsc.next IS TextFrames.Frame) THEN ff := Oberon.FocusViewer.dsc.next(TextFrames.Frame); pos := ff.carloc.pos ELSE ff := NIL END; Oberon.FocusViewer.handle(Oberon.FocusViewer, copyOver); IF (ff # NIL) & CaretVisible(ff, pos+len) THEN TextFrames.SetSelection(ff, pos, pos+len) END ELSIF (msg.keys = {ML, MR}) & tf.hasSel & ~PartialFolds(tf.text, tf.selbeg.pos, tf.selend.pos) THEN Oberon.PassFocus(Viewers.This(tf.X, tf.Y)); Texts.Delete(tf.text, tf.selbeg.pos, tf.selend.pos); TextFrames.SetCaret(tf, tf.selbeg.pos) END ELSE handled := FALSE END ELSIF (msg.id = Oberon.consume) & tf.hasCar THEN loc := tf.carloc; pos := loc.pos; CASE msg.ch OF | CR: msg.ch := LF; handled := FALSE (* switch CR <-> LF *) | LF: msg.ch := CR; handled := FALSE | BS, CtrlD: IF pos < t.len THEN Texts.Delete(t, pos, pos+1); TextFrames.SetCaret(tf, pos) END | CtrlN: msg.ch := DnArrow; handled := FALSE | CtrlP: msg.ch := UpArrow; handled := FALSE | DnArrow, CtrlN: (* IF loc.y-loc.dy <= tf.Y+tf.bot THEN (* at bottom of f *) TextFrames.Show(tf, TextFrames.Pos(tf, loc.x, tf.Y+tf.bot+tf.H-tf.top)); TextFrames.SetCaret(tf, TextFrames.Pos(tf, loc.x+1, loc.y)) ELSE y := loc.y-loc.dy; REPEAT pos := TextFrames.Pos(tf, loc.x+1, y); DEC(y) UNTIL (pos # loc.pos) OR (pos >= t.len) OR (y <= tf.Y+tf.bot); TextFrames.SetCaret(tf, pos) END TextFrames.LocatePos(tf, t.len, loc2); LOOP y := loc.y+1; REPEAT DEC(y); pos := TextFrames.Pos(tf, loc.x+1, y) UNTIL (pos # loc.pos) OR (pos >= t.len) OR (y <= tf.Y+tf.bot); IF pos # loc.pos THEN TextFrames.SetCaret(tf, pos); EXIT (* position found *) ELSIF (pos >= t.len) OR (y <= loc2.y) THEN EXIT (* last line, no chance of finding one *) END; y := tf.Y+tf.H; REPEAT DEC(y); TextFrames.LocateLine(tf, y, loc) UNTIL loc.org # tf.org; TextFrames.Show(tf, loc.org); TextFrames.LocatePos(tf, tf.carloc.pos, loc) END | UpArrow, CtrlP: IF loc.org = tf.org THEN (* top of frame *) IF tf.org > 0 THEN pos := tf.org-1; TextFrames.Show(tf, pos); TextFrames.SetCaret(tf, TextFrames.Pos(tf, loc.x+1, tf.Y+tf.H)) END ELSE (* not at top *) y := loc.y+loc.dy; REPEAT pos := TextFrames.Pos(tf, loc.x+1, y); INC(y) UNTIL (pos # loc.pos) OR (y >= tf.Y+tf.H); TextFrames.SetCaret(tf, pos) END | CtrlT: IF pos > 1 THEN (* exchange this with previous char *) Texts.Save(t, pos-2, pos-1, wr.buf); Texts.Delete(t, pos-2, pos-1); Texts.Insert(t, pos-1, wr.buf); TextFrames.SetCaret(tf, pos) END | CtrlF: IF pos < t.len THEN (* move one word forward *) Texts.OpenReader(r, t, pos); Texts.Read(r, ch); WHILE ~r.eot & (ch <= " ") & (ch # Texts.ElemChar) DO Texts.Read(r, ch) END; IF r.eot THEN pos := t.len ELSE IF ~InWordSet(ch) THEN pos := Texts.Pos(r) ELSE REPEAT Texts.Read(r, ch) UNTIL r.eot OR ~InWordSet(ch); IF r.eot THEN pos := t.len ELSE pos := Texts.Pos(r)-1 END; TextFrames.LocatePos(tf, pos, loc); IF loc.y <= tf.Y THEN TextFrames.Show(tf, pos) END; (* at bottom of f *) END END; TextFrames.SetCaret(tf, pos) END | CtrlB: IF pos > 0 THEN (* move one word backward *) Texts.OpenReader(r, t, pos); REPEAT BackRead(r, t, ch) UNTIL (Texts.Pos(r) = 0) OR (ch > " ") OR (ch = Texts.ElemChar); IF Texts.Pos(r) = 0 THEN pos := 0 ELSE IF ~InWordSet(ch) THEN pos := Texts.Pos(r) ELSE REPEAT BackRead(r, t, ch) UNTIL (Texts.Pos(r) = 0) OR ~InWordSet(ch); IF Texts.Pos(r) = 0 THEN pos := 0 ELSE pos := Texts.Pos(r)+1 END END END; IF pos < tf.org THEN TextFrames.Show(tf, pos) END; TextFrames.SetCaret(tf, pos) END | CtrlE: IF pos < t.len THEN (* move to end of (next) line *) Texts.OpenReader(r, t, pos); Texts.Read(r, ch); IF ~r.eot & (ch = CR) THEN Texts.Read(r, ch) END; WHILE ~r.eot & (ch # CR) DO Texts.Read(r, ch) END; IF r.eot THEN TextFrames.SetCaret(tf, t.len) ELSE TextFrames.SetCaret(tf, Texts.Pos(r)-1) END END | CtrlW: IF pos > 0 THEN (* move to beginning of (previous) line *) IF pos = loc.org THEN TextFrames.LocatePos(tf, pos-1, loc) END; TextFrames.SetCaret(tf, loc.org) END | CtrlK: (* delete to end of line *) Texts.OpenReader(r, t, pos); REPEAT Texts.Read(r, ch) UNTIL r.eot OR (ch = CR); IF Texts.Pos(r) = pos+1 THEN Texts.Delete(t, pos, pos+1) ELSE Texts.Delete(t, pos, Texts.Pos(r)-1) END; TextFrames.SetCaret(tf, pos) | CtrlX: (* move selection to caret position *) Oberon.GetSelection(sel, beg, end, time); IF time >= 0 THEN MoveTextStretch(sel, tf, beg, end, pos) END | CtrlZ: IF pos < t.len THEN (* delete forward to non-char *) Texts.OpenReader(r, t, pos); Texts.Read(r, ch); WHILE ~r.eot & (ch <= " ") & (ch # Texts.ElemChar) DO Texts.Read(r, ch) END; IF r.eot THEN end := t.len ELSE IF ~InWordSet(ch) THEN end := Texts.Pos(r) ELSE REPEAT Texts.Read(r, ch) UNTIL r.eot OR ~InWordSet(ch); IF r.eot THEN end := t.len ELSE end := Texts.Pos(r)-1 END END END; Texts.Delete(t, pos, end); TextFrames.SetCaret(tf, pos) END ELSE handled := FALSE END (* CASE msg.ch ... *) ELSE handled := FALSE END (* IF msg.id = ... *) | msg: Oberon.CopyOverMsg DO (* allow copy over only if text has no partial folds in it *) IF ~tf.hasCar OR ~PartialFolds(msg.text, msg.beg, msg.end) THEN handled := FALSE END ELSE handled := FALSE END; (* WITH msg: ... *) IF ~handled THEN IF (msg IS TextFrames.UpdateMsg) & (msg(TextFrames.UpdateMsg).id = Texts.replace) & tf.hasSel THEN keepSel := TRUE; beg := tf.selbeg.pos; end := tf.selend.pos ELSE keepSel := FALSE END; TextFrames.Handle(tf, msg); IF ~tf.hasSel & keepSel THEN TextFrames.SetSelection(tf, beg, end) END END END Handle; (** Commands *) PROCEDURE OpenModViewer(scanName: ARRAY OF CHAR; scanClass: INTEGER; VAR f: TextFrames.Frame); VAR v: Viewers.Viewer; t: Texts.Text; name, extension: LongName; x, y: INTEGER; BEGIN OpenText(t, name, scanName, scanClass, empty[0], ext[0, 0], ext[0, 1]); Oberon.AllocateUserViewer(Oberon.Mouse.X, x, y); f := TextFrames.NewText(t, 0); Extension(name, extension); IF extension = ext[0, 1] THEN v := MenuViewers.New(MenuFrame(name, TextMenu, TextMenuNum), f, TextFrames.menuH, x, y) ELSE v := MenuViewers.New(MenuFrame(name, DefMenu, DefMenuNum), f, TextFrames.menuH, x, y) END; v.dsc.handle := Handle; v.dsc.next.handle := Handle END OpenModViewer; PROCEDURE Open*; (** (name | "^") Open a user viewer containing a text *) VAR s: Texts.Scanner; f: TextFrames.Frame; BEGIN ScanFirst(s); OpenModViewer(s.s, s.class, f); SearchIdent(f) END Open; PROCEDURE SysOpen*; (** [defY] (name | "^") Open a system viewer at defY *) VAR v: Viewers.Viewer; t: Texts.Text; s: Texts.Scanner; name: LongName; x, defY, y: INTEGER; default: BOOLEAN; BEGIN ScanFirst(s); IF s.class = Texts.Int THEN (* read desired Y-coordinate *) defY := SHORT(s.i); default := TRUE; Oberon.Par.pos := Texts.Pos(s)-1; ScanFirst(s) ELSE default := FALSE END; OpenText(t, name, s.s, s.class, empty[1], ext[1, 0], ext[1, 1]); Oberon.AllocateSystemViewer(Oberon.Mouse.X, x, y); IF default THEN y := defY END; v := MenuViewers.New(MenuFrame(name, ToolMenu, ToolMenuNum), TextFrames.NewText(t, 0), TextFrames.menuH,x,y); v.dsc.handle := Handle; v.dsc.next.handle := Handle END SysOpen; PROCEDURE OpenWide*; (** (name | "^") Open a user viewer containing a text spanning whole display *) VAR v: Viewers.Viewer; t: Texts.Text; s: Texts.Scanner; name: LongName; x, y: INTEGER; BEGIN ScanFirst(s); OpenText(t, name, s.s, s.class, empty[2], ext[2, 0], ext[2, 1]); Oberon.OpenTrack(Oberon.UserTrack(Oberon.Mouse.X), Oberon.DisplayWidth(Oberon.Mouse.X)); Oberon.AllocateUserViewer(Oberon.Mouse.X, x, y); v := MenuViewers.New(MenuFrame(name, WideMenu, WideMenuNum), TextFrames.NewText(t, 0), TextFrames.menuH, x, y); v.dsc.handle := Handle; v.dsc.next.handle := Handle; SearchIdent(v.dsc.next(TextFrames.Frame)) END OpenWide; PROCEDURE OpenAscii*; (** (name | "^") Open a user viewer containing a text using a fixed spaced font *) VAR v: Viewers.Viewer; t: Texts.Text; s: Texts.Scanner; name: LongName; x, y: INTEGER; BEGIN ScanFirst(s); OpenText(t, name, s.s, s.class, empty[3], ext[3, 0], ext[3, 1]); Texts.ChangeLooks(t, 0, t.len, {0}, Fonts.This(AsciiFont), 0, 0); Oberon.AllocateUserViewer(Oberon.Mouse.X, x, y); v := MenuViewers.New(MenuFrame(name, AsciiMenu, AsciiMenuNum), TextFrames.NewText(t, 0), TextFrames.menuH, x, y); v.dsc.handle := Handle; v.dsc.next.handle := Handle; SearchIdent(v.dsc.next(TextFrames.Frame)) END OpenAscii; PROCEDURE Error*; (** Show next error after caret *) VAR f: TextFrames.Frame; v: Viewers.Viewer; pos: LONGINT; e: Texts.Elem; r: Texts.Reader; BEGIN IF Oberon.Par.vwr.dsc = Oberon.Par.frame THEN (* called from menu frame *) IF (Oberon.Par.frame.next # NIL) & (Oberon.Par.frame.next IS TextFrames.Frame) THEN f := Oberon.Par.frame.next(TextFrames.Frame) ELSE f := NIL END ELSE v := Oberon.MarkedViewer(); IF (v.dsc # NIL) & (v.dsc.next # NIL) & (v.dsc.next IS TextFrames.Frame) THEN f := v.dsc.next(TextFrames.Frame) ELSE f := NIL END END; IF f # NIL THEN IF f.hasCar THEN pos := f.carloc.pos ELSE pos := 0 END; IF f.hasCar & (pos > 0) THEN (* delete error elem before caret *) Texts.OpenReader(r, f.text, pos-1); Texts.ReadElem(r); IF (r.elem # NIL) & (Texts.ElemPos(r.elem) = pos-1) & (ErrCheck(r.elem)) THEN Texts.Delete(f.text, pos-1, pos); DEC(pos) END END; FoldElems.FindElem(f.text, pos, ErrCheck, e); IF e # NIL THEN pos:=Texts.ElemPos(e); (*TextFrames.*)Show(f, pos); e(Elem).pos := pos; UpdateErr(e(Elem)); Oberon.PassFocus(Viewers.This(f.X, f.Y)); TextFrames.SetCaret(f, pos+1) ELSIF f.hasCar THEN TextFrames.RemoveCaret(f) END END END Error; PROCEDURE Compile*; (** [options] | "*" | "^" | {fileName [options]} Compile viewer in main frame with options or marked viewer or list of filenames with options *) VAR f: TextFrames.Frame; menuT: Texts.Text; s: Texts.Scanner; v: Viewers.Viewer; len: LONGINT; options: Name; fileName: LongName; error: BOOLEAN; PROCEDURE Comp(frame: TextFrames.Frame; text: Texts.Text; name: ARRAY OF CHAR); VAR vwr: MenuViewers.Viewer; logLen: LONGINT; x, y, h: INTEGER; this, prev: Element; ext: Name; errorFile: LongName; res: INTEGER; sc: Texts.Scanner; empty: BOOLEAN; BEGIN COPY(DefErrFile, errorFile); IF (name # "") OR (frame = NIL) THEN (* no compile command yet or compile command supplied, check extension *) empty := compiler = ""; IF empty THEN COPY(defComp, compiler) END; Extension(name, ext); this := SearchPair(ext, prev); IF this # NIL THEN IF empty THEN COPY(this.compiler, compiler) END; COPY(this.errFile, errorFile) END END; errT := TextFrames.Text(errorFile); PrepareText(text); IF frame = NIL THEN (* create temporary viewer *) x := Display.Width-1; y := Display.Bottom; h := Viewers.minH; Viewers.minH := 1; vwr := MenuViewers.New(TextFrames.NewMenu("", ""), TextFrames.NewText(text, 0), TextFrames.menuH, x, y ); Oberon.Pointer.X := x; Oberon.Pointer.Y := y; Viewers.minH := h; viewerComp := TRUE ELSE DeleteErrElems(text); viewerComp := FALSE END; (* create new parameter text for compiler *) Oberon.Par.text := TextFrames.Text(""); Oberon.Par.pos := 0; Ch("*"); Str(options); Texts.Append(Oberon.Par.text, wr.buf); Str(compiler); Ch(" "); Str(options); Texts.Append(Oberon.Log, wr.buf); Append(".Compile", compiler); (* extend compiler command, if necessary *) logLen := Oberon.Log.len; Oberon.Call(compiler, Oberon.Par, FALSE, res); IF res = 0 THEN IF frame # NIL THEN MarkErrors(frame, logLen) ELSE error := CheckErrors(sc, logLen) END END; RestoreText; IF frame = NIL THEN Viewers.Close(vwr); IF error THEN OpenModViewer(name, Texts.Name, frame); PrepareText(frame.text); MarkErrors(frame, logLen); RestoreText; Oberon.Pointer.X := frame.X+frame.W DIV 2; Oberon.Pointer.Y := frame.Y+frame.H DIV 2; Error END END END Comp; BEGIN menuT := NIL; IF Oberon.Par.vwr.dsc = Oberon.Par.frame THEN (* called from menu frame *) IF (Oberon.Par.frame.next # NIL) & (Oberon.Par.frame.next IS TextFrames.Frame) THEN f := Oberon.Par.frame.next(TextFrames.Frame); menuT := Oberon.Par.frame(TextFrames.Frame).text (* menu text *) END ELSE (* allow XE.Compile * ... *) ScanFirst(s); IF (s.class = Texts.Char) & (s.c = "*") & (s.line = 0) THEN Oberon.Par.pos := Texts.Pos(s); v := Oberon.MarkedViewer(); IF (v IS MenuViewers.Viewer) & (v.dsc.next # NIL) & (v.dsc.next IS TextFrames.Frame) THEN f := v.dsc.next(TextFrames.Frame); menuT := v.dsc(TextFrames.Frame).text (* menu text *) END END END; IF menuT # NIL THEN compiler := ""; fileName := ""; IF menuT # Oberon.Par.text THEN (* called from an element *) ScanFirst(s); IF (s.class = Texts.Name) & (s.line = 0) THEN (* get compiler override name *) COPY(s.s, compiler); Texts.Scan(s) END; GetOptions(s, options) END; IF compiler = "" THEN Texts.OpenScanner(s, menuT, 0); Texts.Scan(s); IF s.class = Texts.Name THEN COPY(s.s, fileName) END END; len := menuT.len; Oberon.DrawCursor(Oberon.Pointer, Oberon.Star, f.X, f.Y); Oberon.FadeCursor(Oberon.Pointer); Comp(f, f.text, fileName); IF len # menuT.len THEN (* text was stored and got an UpdateMsg -> ! char in menu text *) Texts.Delete(menuT, menuT.len-1, menuT.len) END; Error (* show first error, if any *) ELSE (* compile file list: {fileName [options] } ~ *) ScanFirst(s); error := FALSE; WHILE (s.class = Texts.Name) & ~error DO COPY(s.s, fileName); Texts.Scan(s); GetOptions(s, options); compiler := ""; Comp(NIL, TextFrames.Text(fileName), fileName) END END END Compile; PROCEDURE List*; (** List compiler, extension, errorfile set *) VAR this: Element; PROCEDURE WriteExt(cmd: ARRAY OF CHAR; i: INTEGER); BEGIN Str(cmd); Str(" - "); Str(empty[i]); Str(" *."); Str(ext[i, 0]); Str(" *."); Str(ext[i, 1]); Ln END WriteExt; BEGIN Str("XE.List"); Ln; Str("OpenCmd - "); Str(openCmd); Ln; WriteExt("Open", 0); WriteExt("SysOpen", 1); WriteExt("OpenWide", 2); WriteExt("OpenAscii", 3); Str("Compiler"); Ln; IF defComp # "" THEN Str(defComp); Str(" - * - "); Str(DefErrFile); Ln END; this := root; WHILE this # NIL DO Str(this.compiler ); Str(" - *."); Str(this.ext); Str(" - "); Str(this.errFile); Ln; this := this.next END END List; PROCEDURE Defaults*; (** Clear compiler, extension, errorfile set and load default assignments from configuration file *) VAR t: Texts.Text; s: Texts.Scanner; new, this, prev: Element; PROCEDURE ScanExt(cmd: ARRAY OF CHAR; i: INTEGER); BEGIN IF (s.class = Texts.Name) & (s.s = cmd) THEN Texts.Scan(s); IF (s.class = Texts.Name) OR (s.class = Texts.String) THEN COPY(s.s, empty[i]); Texts.Scan(s) END; IF (s.class = Texts.Name) OR (s.class = Texts.String) THEN COPY(s.s, ext[i, 0]); Texts.Scan(s) END; IF (s.class = Texts.Name) OR (s.class = Texts.String) THEN COPY(s.s, ext[i, 1]); Texts.Scan(s) END END END ScanExt; BEGIN root := NIL; defComp := DefComp; openCmd := DefOpenCmd; empty[0] := Empty0; ext[0, 0] := Ext00; ext[0, 1] := Ext01; empty[1] := Empty1; ext[1, 0] := Ext1; ext[1, 1] := Ext1; empty[2] := Empty0; ext[2, 0] := Ext00; ext[2, 1] := Ext01; empty[3] := Empty3; ext[3, 0] := Ext30; ext[3, 1] := Ext31; t := TextFrames.Text(ConfigurationName); IF t.len # 0 THEN Texts.OpenScanner(s, t, 0); Texts.Scan(s); IF (s.class = Texts.Name) & (s.s = "OpenCmd") THEN Texts.Scan(s); IF (s.class = Texts.Name) OR (s.class = Texts.String) THEN COPY(s.s, openCmd); Texts.Scan(s) END END; ScanExt("Open", 0); ScanExt("SysOpen", 1); ScanExt("OpenWide", 2); ScanExt("OpenAscii", 3); IF (s.class = Texts.Name) & (s.s = "Compiler") THEN Texts.Scan(s); WHILE ~s.eot & (s.class = Texts.Name) DO NEW(new); COPY(s.s, new.compiler); Texts.Scan(s); IF (s.class = Texts.Char) & (s.c = "*") THEN defComp := new.compiler ELSIF s.class = Texts.Name THEN COPY(s.s, new.ext); Texts.Scan(s); IF s.class = Texts.Name THEN COPY(s.s, new.errFile); errT := TextFrames.Text(new.errFile); IF errT.len = 0 THEN errT := TextFrames.Text(DefErrFile); COPY(DefErrFile, new.errFile) END ELSE errT := TextFrames.Text(DefErrFile); COPY(DefErrFile, new.errFile) END; this := SearchPair(new.ext, prev); (* check for duplicates *) IF this = NIL THEN new.next := root; root := new (* new entry *) ELSIF this.compiler # new.compiler THEN (* new entry for existing extension -> remove this *) IF this = root THEN new.next := root.next; root := new ELSE new.next := this.next; prev.next := new END END END; Texts.Scan(s) END END END END Defaults; PROCEDURE GetHandler*; (** install XE.Handle in Oberon.Par.frame.handle, if Oberon.Par.pos = GetHandlerKey *) BEGIN IF (Oberon.Par # NIL) & (Oberon.Par.pos = GetHandlerKey) & (Oberon.Par.frame # NIL) THEN Oberon.Par.frame.handle := Handle END END GetHandler; BEGIN Texts.OpenWriter(wr); errFnt := Fonts.This(ErrFont); first := TRUE; cleanup.text := NIL; NEW(cleanup.task); cleanup.task.safe := FALSE; cleanup.task.handle := RestoreText; cleanup.task.time := 0; Defaults; delay := Host.TimeUnit DIV 2 (* 500 ms *) END XE.