//[of]:license //[c] Code Browser - a folding text editor for programmers //[c] Copyright (C) 2003-07 Marc Kerbiquet //[c] //[c] This program is free software; you can redistribute it and/or modify //[c] it under the terms of the GNU General Public License as published by //[c] the Free Software Foundation; either version 2 of the License, or //[c] (at your option) any later version. //[c] //[c] This program is distributed in the hope that it will be useful, //[c] but WITHOUT ANY WARRANTY; without even the implied warranty of //[c] MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //[c] GNU General Public License for more details. //[c] //[c] You should have received a copy of the GNU General Public License //[c] along with this program; if not, write to the Free Software //[c] Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //[cf] //[of]:imports import "base/types" import "base/memory" import "base/memory-allocator" import "text/string" import "text/string-buffer" import "collection/vector" import "net/uri" import "file/file" //[c] import "libc/stdio" import "os" //[c] import "utilities/text-utils" import "utilities/file-utils" import "editor/text-model" //[c] import "strings-en" //[cf] //[of]:private //[of]:undo redo stack //[of]:description //[c]Undo Stack //[c] //[c]The undo stack is an object that interacts only with a text object. //[c]- It listens to change from the text object //[c] In fact, it is not a listener, it does not register to any text object. //[c] The owner of the undo stack invoke explicitely the event routines (handle *) //[c]- It performs operation on the text object //[c]- It can remember a save position that can be set or retrieved using //[c] 'synchronize' and 'is synchronized' //[c] //[c]An undo stack can stack changes from several text objects. //[c] //[c]The current implementation uses two stacks: one for undo actions and one //[c]for redo actions. //[c] //[c]This object is visible only by the text file object. To use these services, //[c]check text file methods. //[cf] //[of]:private //[of]:undo stack //[of]:definition //[c] public struct undo stack // buffer to store undo items private buf: [] byte // size of buffer private buf size: size // current position in buffer private p : [] byte // current folder // i.e. the folder for the last pushed item public folder: text object // associated to the folder // the folder location is pushed late, but // serialization must be done early or it can be wrong private folder location : undo location item end //[cf] //[c] //[of]:initialize - release //[of]:reset (m) //[c]Reset //[c] func reset (m: undo stack) // garbage all items in the stack def p = p (m) def top = top (m) while p < top p = release (p:undo item) end p (m) = top folder (m) = nil end //[cf] //[of]:initialize (m) //[c] func initialize (m: undo stack) buf (m) = allocate memory (default stack size) buf size (m) = default stack size p (m) = top (m) folder (m) = nil folder location (m) = nil end //[cf] //[of]:release (m) //[c] func release (m: undo stack) // release all items reset (m) // free folder location free folder location (m) // free the stack free memory (buf (m)) end //[cf] //[cf] //[of]:operations //[of]:undo (m) //[c]Perform undo //[c] //[c]Returns true if the end of a group has been encountered (and skipped). //[c] private func undo (m: undo stack) : bool // the stack is empty, nothing to cancel if is empty (m) return true end // pop any relocation item and adjust folder next folder (m) def p = p (m) : undo item switch type (p) case undo replace undo (m, p : undo replace item) // commented out: the pointer is updated in the replace text // event of the undo-redo-stack object // p (m) = release (p) case undo folder properties undo (m, p : undo folder prop item) case undo link properties undo (m, p : undo link prop item) case undo stop group undo stop group (m) case undo start group undo start group (m) return true end return false end //[cf] //[of]:undo stop group (m) //[c]Undo a stop group //[c] //[c]Start a group and undo all until the original start group. //[c] private func undo stop group (m: undo stack) start group (folder (m)) repeat if undo (m) return end end end //[cf] //[of]:undo start group (m) //[c]Undo a start group //[c] private func undo start group (m: undo stack) stop group (folder (m)) end //[cf] //[of]:undo (m, undo location item) //[c]Undo a relocation //[c] func undo (m: undo stack, item: undo location item) def text = root folder (m) def i = 0 : size def n = depth (item) while i < n def line number = line numbers (item) [i] // scan for the right line def line = first line (text) def count = 0 : line number while count < line number line = next (line) ++count end text = text object (line) ++i end folder (m) = text def p = allocate folder location (m, n) copy (p, item, size (item)) end //[cf] //[of]:undo (m, undo replace item) //[c]Undo a replace //[c] func undo (m: undo stack, i: undo replace item) def start : local text position text line (start) = text line (folder (m), start line (i)) column (start) = start column (i) line number (start) = start line (i) def limit : local text position text line (limit) = text line (folder (m), stop line (i)) column (limit) = stop column (i) line number (limit) = stop line (i) replace text ( folder (m), start, limit, first (lines (i))) end //[cf] //[of]:undo (m, undo folder prop item) //[c]Undo folder properties //[c] func undo (m: undo stack, i: undo folder prop item) set folder properties ( folder (m), text line (folder (m), line (i)), id (i), title (i)) end //[cf] //[of]:undo (m, undo link prop item) //[c]Undo link properties //[c] func undo (m: undo stack, i: undo link prop item) set link properties ( folder (m), text line (folder (m), line (i)), id (i), title (i), path (i)) end //[cf] //[of]:next folder (m) //[c]Get the next folder //[c] //[c]This method can be called before calling undo to determine in //[c]which folder the next undo action will operate. //[c] func next folder (m: undo stack) if ~ is empty (m) def p = p (m) : undo item if type (p) == undo location undo (m, p : undo location item) p (m) = release (p) end end return folder (m) end //[cf] //[cf] //[of]:adding - removing //[of]:push (m, folder, start line, start col, stop line, stop col, lines) //[c]Push a replace //[c] //[c]The undo stack takes the control of lines: it means it is its //[c]responsibility to destroy them. //[c] func push ( m: undo stack, folder: text object, start line : int, start column : int, stop line : int, stop column : int, lines: list of lines, can merge: bool) /* * Special optimization: several adjacent insertions are packed in * the undo stack to avoid consuming to much memory. */ def fl = first (lines) if can merge && // never merge when synchronized ~ is empty (m) && // there is a previous item folder == folder (m) && // in the same folder fl == last (lines) && size (fl) == 0 && // it is an insertion (no delete) start line == stop line // without cariage return def p = p (m) : undo item if type (p) == undo replace equ i = p : undo replace item if start line == stop line (i) && start column == stop column (i) && // adjacents start line (i) == stop line (i) // to a single line insertion // merge stop column (i) = stop column delete lines (lines) return end end end // update location push (m, folder) def i = allocate (m, sizeof local undo replace item) : undo replace item type (i) = undo replace start line (i) = start line start column (i) = start column stop line (i) = stop line stop column (i) = stop column set (lines (i), lines) end //[cf] //[of]:push (m, folder, replace prop event) //[c]Push a property change //[c] func push ( m: undo stack, folder: text object, e: replace prop event) // update location push (m, folder) def line number = line number (e) if is folder (line object (e)) def i = allocate (m, sizeof local undo folder prop item) : undo folder prop item type (i) = undo folder properties line (i) = line number id (i) = new string (original id (e)) title (i) = new string (original title (e)) else def i = allocate (m, sizeof local undo link prop item) : undo link prop item type (i) = undo link properties line (i) = line number id (i) = new string (original id (e)) title (i) = new string (original title (e)) path (i) = new string (original path (e)) end end //[cf] //[of]:push (m, folder) //[c]Push a relocation //[c] func push (m: undo stack, folder: text object) if folder == folder (m) return end // push old folder // do not push if the stack is empty or the next item is // already a relocation item if not nil (folder (m)) && ~ is empty (m) && type (p (m) : undo item) <> undo location def item = folder location (m) def depth = depth (item) def s = size undo location item (depth) def p = allocate (m, s) : undo location item copy (p, folder location (m), s) end // pass 1 - compute depth def depth = 0 : size def f = parent folder (folder) while not nil (f) ++depth f = parent folder (f) end def p = allocate folder location (m, depth) type (p) = undo location depth (p) = depth // pass 2 - setup line numbers def i = depth f = parent folder (folder) def l = parent line (folder) while not nil (f) line numbers (p) [--i] = line number (l) l = parent line (f) f = parent folder (f) end folder (m) = folder end //[cf] //[of]:push group (m, start) //[c]Push a start or stop group //[c] func push group (m: undo stack, start: bool) // Closing an empty group: just remove the start item def p = p (m) : undo item if ~ start && type (p) == undo start group p (m) = release (p) return end def i = allocate (m, sizeof local undo item) : undo item type (i) = (start -> undo start group, undo stop group) end //[cf] //[cf] //[of]:memory management //[of]:allocate (m, bytes) //[c]Allocate memory on the stack //[c] func allocate (m: undo stack, bytes: size) reserve (m, bytes) p (m) -= bytes return p (m) end //[cf] //[of]:reserve (m, bytes) //[c]Reserve space //[c] func reserve (m: undo stack, bytes: size) if (p (m) - buf (m)):size >= bytes return end // compute the number of bytes used def used = top (m) - p (m) // compute new size def new size = (used + bytes + 1) * 5 / 4 // allocate new buffer def q = allocate memory (new size): [] byte // compute new pointer def new pointer = q + new size - used // copy the old buffer into the new one copy (new pointer, p (m), used) // free the old buffer free memory (buf (m)) // update object buf (m) = q buf size (m) = new size p (m) = new pointer end //[cf] //[c] //[of]:allocate folder location (m, depth) //[c] func allocate folder location (m: undo stack, depth: size) free folder location (m) def s = size undo location item (depth) def p = allocate memory (s) : undo location item folder location (m) = p return p end //[cf] //[of]:free folder location (m) //[c] func free folder location (m: undo stack) if not nil (folder location (m)) free memory (folder location (m)) end folder location (m) = nil end //[cf] //[cf] //[of]:accessing //[of]:offset without relocation (m) //[c]Compute offset without relocation //[c]Relocations must be ignored by the save position //[c] private func offset without relocation (m: undo stack) def offset = offset (m) if ~ is empty (m) def p = p (m) : undo item if type (p) == undo location offset -= size (p : undo location item) end end return offset end //[cf] //[of]:top (m) //[c] equ top (m: undo stack) = buf (m) + buf size (m) //[cf] //[of]:offset (m) //[c] equ offset (m: undo stack) = buf (m) + buf size (m) - p (m) //[cf] //[of]:root folder (m) //[c]Returns the root folder //[c] func root folder (m: undo stack) def f = folder (m) while not nil (parent folder (f)) f = parent folder (f) end return f end //[cf] //[cf] //[of]:testing //[of]:is empty (m) //[c]Checks if the stack is empty (ignoring relocations) //[c] func is empty (m: undo stack) return p (m) == top (m) end //[cf] //[cf] //[cf] //[of]:undo item //[of]:definition //[c] struct undo item type: enum undo location undo replace undo folder properties undo link properties undo start group undo stop group end end //[cf] //[c] //[of]:release (undo item) //[c] func release (m: undo item) def p = m : [] byte switch type (m) case undo location equ i = m : undo location item p += size (i) case undo replace equ i = m:undo replace item delete lines (lines (i)) p += sizeof local undo replace item case undo folder properties equ i = m:undo folder prop item delete (id (i)) delete (title (i)) p += sizeof local undo folder prop item case undo link properties equ i = m:undo link prop item delete (id (i)) delete (title (i)) delete (path (i)) p += sizeof local undo link prop item case undo start group, undo stop group p += sizeof local undo item end return p end //[cf] //[cf] //[of]:undo replace item //[of]:definition //[c] struct undo replace item : local undo item // replace parameters start line : int start column : int stop line : int stop column : int lines: local list of lines end //[cf] //[cf] //[of]:undo folder prop item //[of]:definition //[c] struct undo folder prop item : local undo item line : int id : string title : string end //[cf] //[cf] //[of]:undo link prop item //[of]:definition //[c] struct undo link prop item : local undo item line : int id : string title : string path : string end //[cf] //[cf] //[of]:undo location item //[of]:definition //[c] struct undo location item : local undo item // number of elements depth: size // 'size' number of lines follow // to give the path to the folder starting // from the root folder. line numbers : [*] line number end //[cf] //[c] //[of]:size undo location item (depth) //[c] equ size undo location item (depth: size) = sizeof local undo location item + depth * sizeof line number //[cf] //[of]:size (undo location item) //[c] func size (m: undo location item) return size undo location item (depth (m)) end //[cf] //[cf] //[cf] //[of]:definition //[c] public struct undo redo stack private mode : enum normal undo mode undoing mode redoing mode end private undo stack : local undo stack private redo stack : local undo stack private save position stack : undo stack private save position offset : int end //[cf] //[c] //[of]:initialize - release //[of]:initialize (m) //[c] public func initialize (m: undo redo stack) mode (m) = normal undo mode initialize (undo stack (m)) initialize (redo stack (m)) synchronize (m) end //[cf] //[of]:release (m) //[c] public func release (m: undo redo stack) release (redo stack (m)) release (undo stack (m)) end //[cf] //[cf] //[of]:event handling //[of]:Description //[c]The text file notifies the undo stack about its folder's changes via //[c]these methods. //[cf] //[c] //[of]:handle replace event (m, text object, replace text event) //[c]Replaces text event: put to undo stack //[c] public func handle replace event ( m: undo redo stack, o: text object, e: replace text event) push ( stack (m), o, line number (new start (e)), column (new start (e)), line number (new limit (e)), column (new limit (e)), original content (e), ~ is synchronized (m)) remove all (original content (e)) update save position (m) end //[cf] //[of]:handle prop changed (m, text object, replace prop event) //[c] public func handle prop changed ( m: undo redo stack, o: text object, e: replace prop event) push (stack (m), o, e) update save position (m) end //[cf] //[of]:handle group event (m, text object, start) //[c]Replaces text event: put to undo stack //[c] public func handle group event ( m: undo redo stack, o: text object, s: bool) push group (stack (m), s) update save position (m) end //[cf] //[of]:handle destroyed (m, text object) //[c] public func handle destroyed (m: undo redo stack, o: text object) // ignored end //[cf] //[cf] //[of]:operations //[of]:cancel (m, redo) //[c]Cancels the last action //[c] //[c]If the redo argument is set, it is a redo action, otherwise it is //[c]an undo action. //[c] public func cancel (m: undo redo stack, redo: bool) mode (m) = (redo -> redoing mode, undoing mode) undo (stack (m, redo)) mode (m) = normal undo mode end //[cf] //[of]:synchronize (m) //[c]Synchronize: put the save position with current position //[c] public func synchronize (m: undo redo stack) save position stack (m) = undo stack (m) save position offset (m) = offset without relocation (undo stack (m)) end //[cf] //[of]:reset (m) //[c] public func reset (m: undo redo stack) reset (undo stack (m)) reset (redo stack (m)) synchronize (m) end //[cf] //[cf] //[of]:accessing //[of]:next folder (m, redo) //[c]Retrieves the next folder //[c] public func next folder (m: undo redo stack, redo: bool) return next folder (stack (m, redo)) end //[cf] //[cf] //[of]:testing //[of]:is empty (m, redo) //[c]Returns true if the stack is empty //[c] //[c]redo: if true, the method returns the status of the redo stack //[c]instead of the undo stack. //[c] public func is empty (m: undo redo stack, redo: bool) return is empty (stack (m, redo)) end //[cf] //[of]:is synchronized (m) //[c] public func is synchronized (m: undo redo stack) return ( save position stack (m) == undo stack (m) && save position offset (m) == offset without relocation (undo stack (m)) ) end //[cf] //[c] //[of]:can undo (m) //[c] public func can undo (m: undo redo stack) return ~ is empty (undo stack (m)) end //[cf] //[of]:can redo (m) //[c] public func can redo (m: undo redo stack) return ~ is empty (redo stack (m)) end //[cf] //[cf] //[c] //[of]:private //[of]:constants //[c] private equ default stack size = 16384 //[cf] //[c] //[of]:stack (m) //[c] private func stack (m: undo redo stack) if mode (m) == undoing mode return redo stack (m) else return undo stack (m) end end //[cf] //[of]:stack (m, redo) //[c] private func stack (m: undo redo stack, redo: bool) return redo -> redo stack (m), undo stack (m) end //[cf] //[of]:update save position (m) //[c]Updates the save position, resets the redo stack if required //[c] private func update save position (m: undo redo stack) switch mode (m) case normal undo mode def s = redo stack (m) if save position stack (m) == s save position stack (m) = nil end reset (s) case undoing mode def s = undo stack (m) def t = redo stack (m) if save position stack (m) == s && save position offset (m) == offset (s) save position stack (m) = t save position offset (m) = offset (t) end p (s) = release (p (s) : undo item) case redoing mode def t = undo stack (m) def s = redo stack (m) if save position stack (m) == s && save position offset (m) == offset (s) save position stack (m) = t save position offset (m) = offset (t) end p (s) = release (p (s) : undo item) end end //[cf] //[cf] //[cf] //[cf] //[of]:definitions //[c] public equ text style unix = 1 public equ text style dos = 2 public equ text style mac = 4 // Number of classes of words for syntax highlighting // Do not change: the config file reader has four hardcoded attribute names public equ number of word lists = 4 // Number of classes of chars for syntax highlighting // Do not change: the config file reader has four hardcoded attribute names public equ number of char lists = 4 //[c] public struct text file error code : text file error code filename : string line number : line number end //[c] public struct text file filename : string last modification : time text : text object ignore directives : bool directive prefix: string directive suffix: string // If this flag is set, subfolders will be recursively un-indented // according to the indentation of their headline. // // This value must not be modified between load and save, // otherwise the file would be incorrectly saved. // // It is safe since this value is set at initialization and is // never modified afterward. // relative indentation : bool // The style of the text file is detected when loading // it can be: // LF -- Unix, Amiga // CR -- Mac // CR+LF -- Dos text style : int // Read only flag - depends on the RO state of the file on disk read only : bool private text listener : local text listener private undo redo stack : local undo redo stack private listeners : local vector // error information for the last I/O operation last error : local text file error end //[cf] //[c] //[of]:text file //[of]:description //[c]Object that store a file edited by the application //[cf] //[c] //[of]:instance creation //[of]:new text file (format, filename, folder) //[c] public func new text file ( format: text file format, filename: string, folder: text object) def m = allocate memory (sizeof local text file) : text file initialize (m, format, filename, folder) return m end //[cf] //[of]:new text file (format, filename) //[c] public func new text file ( format: text file format, filename: string) def m = allocate memory (sizeof local text file) : text file initialize (m, format, filename) return m end //[cf] //[of]:new text file (format) //[c] public func new text file (format: text file format) def m = allocate memory (sizeof local text file) : text file initialize (m, format) return m end //[cf] //[c] //[of]:delete (m) //[c] public func delete (m: text file) release (m) free memory (m) end //[cf] //[cf] //[c] //[of]:initialize - release //[of]:initialize (format, filename, folder) public func initialize ( m: text file, format: text file format, filename: string, text object: text object) recipient (text listener (m)) = m text changed (text listener (m)) = ^handle replace event (text file, text object, replace text event) prop changed (text listener (m)) = ^handle prop changed (text file, text object, replace prop event) group changed (text listener (m)) = ^handle group event (text file, text object, bool) destroyed (text listener (m)) = ^handle destroyed (text file, text object) marker added (text listener (m)) = ^ignore marker (object, text object, text marker) marker removed (text listener (m)) = ^ignore marker (object, text object, text marker) /*def prefix = line comment (language) def suffix = empty string if is empty (prefix) prefix = open comment (language) suffix = close comment (language) end*/ directive prefix (m) = new string (directive prefix (format)) directive suffix (m) = new string (directive suffix (format)) relative indentation (m) = relative indentation (format) filename (m) = new string (filename) last modification (m) = nil time text (m) = nil text style (m) = 0 read only (m) = false initialize (last error (m)) initialize (undo redo stack (m)) initialize (listeners (m), 4) set text (m, text object) end //[cf] //[of]:initialize (format, filename) public func initialize ( m: text file, format: text file format, filename: string) def text object = new text object with text line (empty string, empty string) if can structure (format) add (text object, new text line (0, lt comment)) end initialize (m, format, filename, text object) end //[cf] //[of]:initialize (format) public func initialize (m: text file, format: text file format) initialize (m, format, empty string) end //[cf] //[of]:release public func release (m: text file) // The text object must be released before the list of listeners: // some observers of the text will unsubcribe to the file delete (text (m)) delete (directive prefix (m)) delete (directive suffix (m)) release (last error (m)) release (listeners (m)) release (undo redo stack (m)) delete (filename (m)) end //[cf] //[cf] //[of]:event handling //[of]:- Description - //[c]Catch events from any child folders //[c] //[c]Events from the root folder and its child folders are caught. //[c]The undo-redo stack is notified and a file-changed message is emitted //[c]if the synchronization flags has just changed. //[cf] //[c] //[of]:handle replace event (m, folder, replace text event) //[c]Replace text event: put to undo stack //[c] public func handle replace event ( m: text file, o: text object, e: replace text event) equ s = undo redo stack (m) def synced = is synchronized (s) handle replace event (s, o, e) if synced <> is synchronized (s) notify file changed (m, ~ is synchronized (s)) end end //[cf] //[of]:handle prop changed (m, folder, replace prop event) //[c] public func handle prop changed ( m: text file, o: text object, e: replace prop event) equ s = undo redo stack (m) def synced = is synchronized (s) handle prop changed (undo redo stack (m), o, e) if synced <> is synchronized (s) notify file changed (m, ~ is synchronized (s)) end end //[cf] //[of]:handle group event (m, folder, start) //[c]Replace text event: put to undo stack //[c] public func handle group event ( m: text file, o: text object, start: bool) equ s = undo redo stack (m) def synced = is synchronized (s) handle group event (s, o, start) if synced <> is synchronized (s) notify file changed (m, ~ is synchronized (s)) end end //[cf] //[of]:handle destroyed (m, text object) //[c] public func handle destroyed (m: text file, o: text object) handle destroyed (undo redo stack (m), o) end //[cf] //[cf] //[of]:input - output //[of]:load (m, filename) //[c]Initialize the content of the text file object by loading text from a file //[c] //[c]Remark //[c]filename must by fully qualified //[c] public func load (m: text file, filename: string, ignore directives: bool) def result := load text object (m, filename, ignore directives) def text = text (result) def code = code (result) if is ok (code) // Everything is ok, update the state of the object set filename (m, filename) set text (m, text) update last modification time (m) update read only (m) ignore directives (m) = ignore directives else delete (text) end reset (last error (m), code, filename) return code end //[cf] //[of]:save (m, filename) //[c] public func save (m: text file, filename: string) // open the file def f = fopen (filename, "wb") // write all lines def code : text file error code if is nil (f) code = text file error save else code = write lines (m, text (m), f) // close the file fclose (f) end set filename (m, filename) if is ok (code) // update synchronization synchronize (m) update last modification time (m) end reset (last error (m), code, filename (m)) return code end //[cf] //[c] //[of]:incremental reload (m, filename) //[c]Incremental reload of the file //[c] //[c]Instead of replacing directly the content, the file is loaded in a temporary //[c]object, the difference is applied recursively to the main text file using the //[c]replace-text primitive. //[c] //[c]This way, lot of views on this text file can be preserved. //[c] //[c]Remark //[c]filename must by fully qualified //[c] public func incremental reload (m: text file) def result := load text object (m, filename (m), ignore directives (m)) def text = text (result) def code = code (result) if is ok (code) // apply change to the current buffer synchronize (text, text (m)) // Everything is ok, update the state of the object update last modification time (m) update read only (m) synchronize (m) end delete (text) reset (last error (m), code, filename (m)) return code end //[cf] //[of]:load from buffer (m, language, buffer) public func load from buffer ( m: text file, text: text object, buffer: string) read lines (m, text, buffer, false, true) end //[cf] //[c] //[of]:private //[of]:load text object (m, filename) //[c]Initialize the content of the text file object by loading text from a file //[c] //[c]Remark //[c]filename must by fully qualified //[c] public func load text object (m: text file, filename: string, ignore directives: bool, return result: -> struct code: text file error code text: text object end) def text = new text object (empty string, empty string) // open the file def f = fopen (filename, "rb") // read all lines def code : text file error code if is nil (f) code = text file error open else def size = file size (filename) if size <> 0xFFFFFFFF def buf = allocate memory (size+1) : [] char def count = fread (buf, 1, size, f) if count <> size code = text file error read else buf [size] = nul char line number (last error (m)) = 0 code = read lines (m, text, buf, ignore directives, true) end free memory (buf) end // close the file fclose (f) end code (result) = code text (result) = text end //[cf] //[c] //[of]:write lines (m, text object, file) //[c] private func write lines (m: text file, text: text object, f: file) def indent buffer := string buffer def code = write lines (m, text, f, indent buffer, true) release (indent buffer) return code end private func write lines ( m: text file, text: text object, f: file, indent buffer: string buffer, is root: bool) : text file error code def new line = new line (m) def directive prefix = directive prefix (m) def directive suffix = directive suffix (m) def line = first line (text) def indent string = as string (indent buffer) while not nil (line) switch type (line) case lt comment // do not flush line if empty and last line if size (line) == 0 && is nil (next (line)) // must return immediately since a new line // is added after the switch return text file ok end def t := temp string buffer t << indent string t << directive prefix t << comment prefix t << buf (line) t << directive suffix if fputs (as string (t), f) == EOF return text file error write end release (t) case lt folder def l = text object (line) def title = title (l) // save indentation def s = size (indent buffer) // update indentation if relative indentation (m) def start = title title = skip blanks (title) if start:mem <> title:mem append (indent buffer, start, title - start) indent string = as string (indent buffer) end end def t := temp string buffer t << indent string t << directive prefix t << open folder prefix serialize (t, id (l)) t << field separator serialize (t, title) t << directive suffix t << new line if fputs (as string (t), f) == EOF return text file error write end release (t) write lines (m, text object (line), f, indent buffer, false) def u := temp string buffer u << indent string u << directive prefix u << close folder prefix u << directive suffix if fputs (as string (u), f) == EOF return text file error write end release (u) // restore indentation if relative indentation (m) set size (indent buffer, s) indent string = as string (indent buffer) end case lt link def l = link object (line) def t := temp string buffer t << directive prefix t << link prefix serialize (t, id (l)) t << field separator serialize (t, title (l)) t << field separator serialize (t, path (l)) t << directive suffix if fputs (as string (t), f) == EOF return text file error write end release (t) else if size (indent buffer) > 0 if fputs (indent string, f) == EOF return text file error write end end if fputs (buf (line), f) == EOF return text file error write end end def next = next (line) // Do not append an end of line if // - there is no next line // and // - the line is not a non empty comment if is nil (next) && (type (line) <> lt comment) return text file ok end line = next if fputs (new line, f) == EOF return text file error write end end return text file ok end //[cf] //[of]:read lines (m, text object, file, language, is root) //[c] private struct text file reader directive prefix : string directive suffix : string dps : int dss : int ofps : int lps : int cps : int can structure : bool indent buffer: local string buffer current line : [] char current char : char end //[c] private func read lines ( m: text file, text: text object, buffer: string, ignore directives: bool, root: bool): text file error code def reader: local text file reader current line (reader) = buffer def directive prefix : string def directive suffix : string def can structure : bool if ignore directives directive prefix = empty string directive suffix = empty string can structure = false else directive prefix = directive prefix (m) directive suffix = directive suffix (m) can structure = can structure (m) end directive prefix (reader) = directive prefix directive suffix (reader) = directive suffix def dps = size (directive prefix) def dss = size (directive suffix) dps (reader) = dps dss (reader) = dss ofps (reader) = size (open folder prefix) lps (reader) = size (link prefix) cps (reader) = size (comment prefix) can structure (reader) = can structure initialize (indent buffer (reader)) def code = read lines (m, text, reader, root) release (indent buffer (reader)) return code end //[c] private func read lines ( m: text file, text: text object, reader: text file reader, root: bool): text file error code def directive prefix = directive prefix (reader) def directive suffix = directive suffix (reader) def dps = dps (reader) def dss = dss (reader) def text style = 0 def q = current line (reader) repeat def start = q def limit : [] char def c = q++ [] repeat if c == nul char // no info q -= 1 limit = q break elsif c == \n // amiga - unix limit = q - 1 text style |= text style unix break elsif c == \r limit = q - 1 c = q ++ [] if c == \n // dos text style |= text style dos else // mac q -= 1 c = \r // prevent to be null text style |= text style mac end break end c = q++ [] end ++ line number (last error (m)) def indent buffer = indent buffer (reader) def indent buffer size = size (indent buffer) if indent buffer size > 0 if compare (start, base (indent buffer), indent buffer size) == 0 start += indent buffer size end end def first non blank = skip blanks (start) def len = limit - start if len > 0 || not nul (c) || ~ can structure (reader) if dps > 0 && starts with (first non blank, directive prefix) def content = first non blank + dps if starts with (content, comment prefix) content += cps (reader) len = limit - content // we assume that the line will end with closing sequence if len >= dss len -= dss end add (text, new text line (content, len, lt comment)) elsif starts with (content, close folder prefix) // the rest of the line is ignored if root return unexpected closing folder directive else // a comment line is added at the end of every folder add (text, new empty comment line) current line (reader) = q current char (reader) = c return text file ok end elsif starts with (content, open folder prefix) // we assume that the line will end with closing sequence if len >= dss len -= dss end start[len] = \0 def id := temp string buffer def title := temp string buffer def p = content + ofps (reader) def indent size = first non blank - start append (title, start, indent size) p = unserialize (id, p) if relative indentation (m) p = skip blanks (p) end p = unserialize (title, p) def folder = new text object (as string (id), as string (title)) release (id) release (title) current line (reader) = q def code: text file error code if indent buffer size + indent size > 0 // force relative indentation (to preserve format when saving) relative indentation (m) = true // save indentation def s = size (indent buffer (reader)) // update indentation append (indent buffer (reader), start, indent size) code = read lines (m, folder, reader, false) // restore indentation set size (indent buffer (reader), s) else code = read lines (m, folder, reader, false) end if not ok (code) return code end q = current line (reader) c = current char (reader) add (text, new folder line (folder, text)) elsif starts with (content, link prefix) // we assume that the line will end with closing sequence if len >= dss len -= dss end start [len] = \0 def id := temp string buffer def title := temp string buffer def path := temp string buffer def p = content + lps (reader) p = unserialize (id, p) p = unserialize (title, p) p = unserialize (path, p) def link = new link object (as string (id), as string (title), as string (path)) release (id) release (title) release (path) add (text, new link line (link)) else add (text, start, len) end else add (text, start, len) end end if c == nul char break end end if can structure (reader) // a comment line is added at the end of every folder def t = new text line (0, lt comment) add (text, t) end text style (m) = text style current line (reader) = q current char (reader) = nul char return text file ok end //[cf] //[cf] //[cf] //[of]:operations //[of]:cancel (redo) //[c]Performs a undo or redo //[c] public func cancel (m: text file, redo: bool) cancel (undo redo stack (m), redo) end //[cf] //[of]:synchronize //[c]Forces the synchronization //[c] //[c] This method makes as if the file has just been saved //[c] public func synchronize (m: text file) equ s = undo redo stack (m) if ~ is synchronized (s) synchronize (s) notify file changed (m, ~ is synchronized (s)) end end //[cf] //[of]:update last modification time //[c]Updates the last modification time of the loaded file //[c] //[c]This method is invoked when //[c] - the text file is loaded //[c] - the text file is saved //[c] - the text file is reloaded //[c] - external changes are ignored //[c] public func update last modification time (m: text file) def filename = filename (m) last modification (m) = file last modification (filename) end //[cf] //[of]:update read only public func update read only (m: text file) def ro = is file read only (filename (m)) set read only (m, ro) end //[cf] //[cf] //[of]:resolving //[of]:uri (m, link) //[c]Returns a resolved uri for a link object //[c] //[c]The given text line must be a link object //[c] public func uri (m: text file, line: text line, return uri: uri) initialize (uri) set (uri, path (link object (line))) def base := convert filename to uri (filename (m)) resolve (uri, base) release (base) end //[cf] //[cf] //[of]:adding - removing //[of]:add listener (m, text file listener) //[c] public func add listener (m: text file, listener: text file listener) add (listeners (m), listener) end //[cf] //[of]:remove listener (m, text file listener) //[c] public func remove listener (m: text file, listener: text file listener) remove (listeners (m), listener) end //[cf] //[cf] //[of]:accessing //[of]:cancel folder (redo) //[c]Returns the folder in which the next cancel action will occur //[c] public func cancel folder (m: text file, redo: bool) return next folder (undo redo stack (m), redo) end //[cf] //[c] //[of]:root folder //[c]Returns the root folder of this file //[c] public func root folder (m: text file) return text (m) end //[cf] //[of]:set text (text object) //[c]Changes the root folder //[c] //[c] If there was a previous folder, it is deleted. //[c] It results that all windows editing a folder contained //[c] in this text file are closed. //[c] public func set text (m: text file, text: text object) if not nil (text (m)) remove listener (text (m), text listener (m)) delete (text (m)) end text (m) = text add listener (text (m), text listener (m)) reset (undo redo stack (m)) end //[cf] //[of]:set filename (filename) //[c]Changes the filename //[c] public func set filename (m: text file, filename: string) if filename <> filename (m) delete (filename (m)) filename (m) = new string (filename) notify filename changed (m) end end //[cf] //[of]:set read only (ro) //[c]Changes the read only flag //[c] public func set read only (m: text file, ro: bool) if ro == read only (m) return end read only (m) = ro notify read only changed (m, ro) end //[cf] //[of]:set text file format (language) public func set text file format (m: text file, format: text file format) delete (directive prefix (m)) delete (directive suffix (m)) directive prefix (m) = new string (directive prefix (format)) directive suffix (m) = new string (directive suffix (format)) relative indentation (m) = relative indentation (format) end //[cf] //[c] //[of]:display filename //[c]Returns a displayable filename //[c] public func display filename (m: text file) def filename = filename (m) return not empty (filename) -> filename, untitled filename end //[cf] //[of]:append basename (string buffer) //[c]Appends a description of the text file //[c] public func append basename (m: text file, s: string buffer) append base name (s, display filename (m)) end //[cf] //[of]:append description (string buffer) //[c]Appends a description of the text file //[c] public func append description (m: text file, s: string buffer) append basename (m, s) if is modified (m) s << "*" end end //[cf] //[c] //[of]:new line //[c]Returns the new line for this text file //[c] public func new line (m: text file) def s = text style (m) if s == text style unix return unix new line elsif s == text style dos return dos new line elsif s == text style mac return mac new line end // undefined or several style encountered: use platform default return new line sequence end //[c] private def unix new line := "\n" private def dos new line := "\r\n" private def mac new line := "\r" //[cf] //[of]:set text style (style) public func set text style (m: text file, style: int) text style (m) = style end //[cf] //[cf] //[of]:testing //[of]:is modified //[c]Returns true if the file is modified //[c]i.e. the file is not synchronized with the last save //[c] public equ is modified (m: text file) = ~ is synchronized (undo redo stack (m)) //[cf] //[of]:is read only public func is read only (m: text file) return read only (m) end //[cf] //[of]:can structure //[c]Returns true if special objects can be inserted in the text file //[c] public func can structure (m: text file) return not empty (directive prefix (m)) end //[cf] //[of]:can close public func can close (text file: text file) return not nil (text file) && not empty (filename (text file)) end //[cf] //[c] //[of]:can undo //[c]Can undo ? //[c] public func can undo (m: text file) return can undo (undo redo stack (m)) end //[cf] //[of]:can redo //[c]Can redo ? //[c] public func can redo (m: text file) return can redo (undo redo stack (m)) end //[cf] //[of]:can cancel //[c]Can undo or redo ? //[c] public func can cancel (m: text file, redo: bool) return ~is empty (undo redo stack (m), redo) end //[cf] //[c] //[cf] //[c] //[of]:private //[of]:constants //[c] equ untitled filename = str untitled //[c] def comment prefix := "[c]" def open folder prefix := "[of]" def close folder prefix := "[cf]" def link prefix := "[l]" //[cf] //[c] //[of]:each listener //[c] private equ each listener (m: text file) each (listeners (m)) ? l equ listener = l : text file listener yield (listener) end end //[cf] //[of]:notify file changed //[c] private func notify file changed (m: text file, modified: bool) each listener (m) ? l file changed (l) {recipient (l), m, modified} end end //[cf] //[of]:notify read only changed //[c] private func notify read only changed (m: text file, ro: bool) each listener (m) ? l read only changed (l) {recipient (l), m, ro} end end //[cf] //[of]:notify filename changed //[c] private func notify filename changed (m: text file) each listener (m) ? l filename changed (l) {recipient (l), m} end end //[cf] //[c] //[cf] //[cf] //[of]:text file listener public struct text file listener recipient: object file changed: {object, text file, bool} void filename changed: {object, text file} void read only changed: {object, text file, bool} void end //[c] //[c] Provide a default implementation for listener's methods //[c] public func ignore file changed (m: object, t: text file, modified: bool) end //[c] public func ignore read only changed (m: object, t: text file, ro: bool) end //[c] public func ignore filename changed (m: object, t: text file) end //[cf] //[of]:text file error //[of]:initialize - release //[of]:text file error //[c] public equ text file error (m: text file error) = initialize (m) //[cf] //[c] //[of]:initialize (m) //[c] public func initialize (m: text file error) code (m) = text file ok filename (m) = empty string line number (m) = 0 end //[cf] //[of]:release (m) //[c] public func release (m: text file error) delete (filename (m)) end //[cf] //[c] //[of]:reset (m, text file error code, name) //[c] public func reset (m: text file error, code: text file error code, name: string) delete (filename (m)) code (m) = code filename (m) = new string (name) end //[cf] //[cf] //[of]:copying //[of]:copy (src) //[c] public func set (dst: text file error, src: text file error) delete (filename (dst)) code (dst) = code (src) filename (dst) = new string (filename (src)) line number (dst) = line number (src) end //[cf] //[cf] //[c] //[cf] //[of]:text file error code //[of]:definition //[c] public enum text file error code text file ok // can't open a file text file error open // error while reading file text file error read // can't save the text file text file error save // can't write data text file error write // unexpected closing folder directive unexpected closing folder directive end //[cf] //[of]:testing //[of]:is ok (m) //[c] public equ is ok (code: text file error code) = code == text file ok //[cf] //[of]:not ok (m) //[c] public equ not ok (code: text file error code) = ~ is ok (code) //[cf] //[cf] //[cf] //[of]:text file format //[of]:description //[c]A text file format object contains the information //[c]required to load and save folding/link directives //[cf] //[of]:definition //[c] public struct text file format directive prefix: string directive suffix: string // If this flag is set, subfolders will be recursively un-indented // according to the indentation of their headline. // // This value must not be modified between load and save, // otherwise the file would be incorrectly saved. // // It is safe since this value is set at initialization and is // never modified afterward. // relative indentation : bool end //[cf] //[of]:testing //[of]:can structure (m) //[c]Returns true if special objects can be inserted with this language //[c] public func can structure (m: text file format) return not empty (directive prefix (m)) end //[cf] //[cf] //[cf]