//[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 "base/memory-buffer" import "base/memory-reader" import "collection/collection" import "collection/vector" import "collection/vector-of-integers" import "text/string" import "text/string-buffer" import "os" import "swift" //[c] import "editor/text-model" import "utilities/colorizer-interface" import "utilities/text-utils" //[cf] //[of]:constants //[c]Constants //[c] //[c]Special codes for 'cursor line' in move cursor //[c] public equ last line of text = -1 //[c] //[c]Special codes for 'cursor column' in move cursor //[c] public equ last column of line = -1 //[c] //[c]Delayed scroll command //[c] private enum page command pc none pc adjust pc center end //[cf] //[of]structures:structures //[of]page-position:page position public struct page position // The graphical horizontal offset of the page in pixel. // When word-wrapping is active, this value is null. x: int // The line number line number: int // The subline number // Its value is always zero when word wrapping if off subline: int // The associated line object text line: text line end //[cf] //[of]text-view-position:text view position public struct text view position view: text view line number: int subline: int x: int end //[cf] //[c] //[of]text-view-format:text view format //[c]The text view format contains all settings used to display and edit the text. //[c] public struct text view format // can structure : the text accept special // type of lines (folders, links and comments) can structure : bool // edition: replace tab by spaces ? expand tabulation : bool // indent when creating a new line ? auto indentation : bool // size of one tabulation tabulation size : int // word wrapping word wrap : bool // the fonts text font: font comment font: font folder font: font link font: font // line colorizer colorizer: colorizer // back color workspace color: color text back color: color comment back color: color folder back color: color link back color: color marker colors: [mt size] color // selected back color selected back color: color inactive back color: color highlight back color: color highlight fore color: color // fore color comment fore color: color folder fore color: color link fore color: color // width of the caret in insertion mode caret width: int // prefix and suffix to use for folders and link when pasting as raw text open comment: string close comment: string end //[c] //[cf] //[of]:text view class //[c]Text View Class //[c] struct text view class: local container box class // emit a request to follow a link or enter a folder on enter: {box, event} void // emit a request to leave the current folder on leave: {box, event} void // notify that the page has moved (scrolling) on page changed: {box, event} void // notify that the cursor mode has changed (ins/ovr) on cursor mode: {box, event} void end //[cf] //[of]text-view:text view public struct text view : local container box // private format for structured text cf structured text : clipboard format // the control has the focus ? has focus : bool /* * Listen to the events of the currently edited folder */ text listener : local text listener /* * Selection Behavior * this variable tells how to update selection when * receiving a replace text event. * * default: it is an external update, just correct position * select: block is moved to the beginning of the inserted text and * cursor is moved to the end. * append: block and cursor are moved to the end of the inserted text. */ cursor behavior : selection behavior // -- Text -- // What I'm editing text: text object // How I display it and edit it format: text view format font height: int word wrap: bool // -- View information -- // The page is the frame that shows a part of the text object. page: local page position cursor: local text position block: local text position cursor x: int // cursor mode private overwrite mode : bool // mouse mode private mouse mode : enum tv edit mode tv select mode end private cursor timer: local timer private autoscroll timer: local timer private autoscroll position: local point // blinking state (true = visible) private blinking state : bool // Highlight a line tempoarily private highlight line : text line // sublines for each line // nil when word wrapping is off private sublines: local vector of integers // number of sublines private sublines count: int // position of the mouse cursor when clicking on middle button private query point: local point /* * Pointers */ private ibeam pointer: local cursor private hand pointer: local cursor // Page command // If a page method is invoked (either adjust-page or center-page) // before the control is sized, the command is delayed and will // be played on the first resize. private page command: page command // List of ranges (digested version of markers) private ranges: vector // if false the list of ranges must be rebuilt before // next painting private valid ranges: bool end //[cf] //[cf] //[c] //[of]text-view:text view //[of]:description //[c]The Editor Widget //[cf] //[l]:definition:#structures/text-view //[c] //[of]:instance creation //[of]:new text view (parent box) //[c] public func new text view (parent: box) def view = allocate memory (sizeof local text view) : text view initialize(view, parent) return view end //[cf] //[cf] //[of]:class accessing //[of]:enter event type //[c]Returns the enter event type //[c] //[c]This event is emitted when the user requests to enter into the current //[c]folder or to follow the current link. //[c] public def enter event type : local event type //[cf] //[of]:leave event type //[c]Returns the leave event type //[c] //[c]This event is emitted when the user wants to leave the current folder. //[c] public def leave event type : local event type //[cf] //[of]:page changed event type //[c]Returns the page changed event type //[c] //[c]This event is emitted each time the page has moved. //[c] public def page changed event type : local event type //[cf] //[of]:cursor mode event type //[c]Returns the cursor mode changed event type //[c] //[c]This event is emitted each time the insert key is pressed. //[c] public def cursor mode event type : local event type //[cf] //[cf] //[c] //[of]:editing //[of]:indent selection //[c]Indents the selected lines //[c] public func indent selection (m: text view) if is read only (m) return end def text = text (m) start group (text) def selection = new indent line (m) each line of selection (m) ? line, line number indent line (m, line, line number, selection) end delete (selection) stop group (text) end //[c] //[c]Sub-functions: //[of]: indent line (text view, text line, line number, insert) //[c] func indent line ( m: text view, text line: text line, line number: int, insert: text line) if is folder (text line) def folder = text object (text line) def t := temp string buffer t << buf (insert) << title (folder) set folder properties (text (m), text line, id (folder), as string (t)) release (t) elsif is link (text line) def link = link object (text line) def t := temp string buffer t << buf (insert) << title (link) set link properties (text (m), text line, id (link), as string (t), path (link)) release (t) else def pos : local text position text line (pos) = text line line number (pos) = line number column (pos) = 0 replace text (text (m), pos, pos, insert) end end //[cf] //[of]: new indent line (text view) //[c]Creates a selection that will be inserted at the beginning //[c]of each line //[c] func new indent line (m: text view) def t := temp string buffer def f = format (m) if expand tabulation (f) def n = tabulation size (f) while n > 0 t << \w -- n end else t << \t end def line = new text line (t, lt default) prev (line) = nil next (line) = nil release (t) return line end //[cf] //[cf] //[of]:unindent selection //[c]Unindents the selected lines //[c] public func unindent selection (m: text view) if is read only (m) return end def text = text (m) start group (text) def selection = new unindent line (m) each line of selection (m) ? line, line number unindent line (m, line, line number, selection) end delete (selection) stop group (text) end //[c] //[c]Sub-functions: //[of]: unindent line (text view, text line, line number, empty) //[c] //[of]: skip tab (text view, string) //[c]Returns a pointer after the first tabulation //[c] //[c] If the first char is not a tab char, the n first spaces are skipped //[c] where 'n' is the tabulation size //[c] func skip tab (m: text view, s: string) if s[] == \t return s + 1 else def count = tabulation size (format (m)) while count > 0 && s[] == \w ++ s -- count end return s end end //[cf] //[c] func unindent line ( m: text view, text line: text line, line number: int, empty: text line) if is folder (text line) def folder = text object (text line) def title = skip tab (m, title (folder)) set folder properties (text (m), text line, id (folder), title) elsif is link (text line) def link = link object (text line) def title = skip tab (m, title (link)) set link properties (text (m), text line, id (link), title, path (link)) else def pos 1 : local text position text line (pos 1) = text line line number (pos 1) = line number column (pos 1) = 0 def pos 2 : local text position text line (pos 2) = text line line number (pos 2) = line number def p = buf (text line) column (pos 2) = skip tab (m, p) - p replace text (text (m), pos 1, pos 2, empty) end end //[cf] //[of]: new unindent line (text view) //[c]To unindent selection, the initial blanks of each line //[c]are replaced by an empty selection //[c] func new unindent line (m: text view) def line = new empty default line prev (line) = nil next (line) = nil return line end //[cf] //[cf] //[of]:uppercase selection //[c]Makes selection uppercase //[c] public func uppercase selection (m: text view) if is read only (m) return end if is selection empty (m) return end // Copy the selection in a list of lines def lines := list of lines (cursor (m), block (m)) // convert selection each (lines) ? line to upper (display text (line)) end // replace selection replace selection (m, first (lines), select cursor behavior) delete lines (lines) end //[cf] //[of]:lowercase selection //[c]Makes selection lowercase //[c] public func lowercase selection (m: text view) if is read only (m) return end if is selection empty (m) return end // Copy the selection in a list of lines def lines := list of lines (cursor (m), block (m)) // convert selection each (lines) ? line to lower (display text (line)) end // replace selection replace selection (m, first (lines), select cursor behavior) delete lines (lines) end //[cf] //[c] //[of]:insert comment line public func insert comment line (m: text view) insert or transform (m, lt comment, lt text) end //[cf] //[of]:insert text line public func insert text line (m: text view) insert or transform (m, lt text, lt comment) end //[cf] //[of]:insert folder (id, name, lines) //[c]Insert folder //[c] //[c] Note: the lines are moved from the given list to the folder, they are not copied. //[c] public func insert folder ( m: text view, id: string, name: string, lines: list of lines) // cursor to the beginning of line cursor to (m, line (cursor (m)), 0, false) // prepare folder def folder = new text object (id, name) def count = count (lines) add all (folder, lines, count) // do not pass parent, otherwise the orphan send // unexpected events to the parent //def t1 = new folder line(folder, text (m)) def t1 = new folder line (folder, nil) def t2 = new empty default line prev (t1) = nil next (t1) = t2 prev (t2) = t1 next (t2) = nil replace selection (m, t1, default cursor behavior) delete (t1) delete (t2) end //[cf] //[of]:insert link (id, name, path) //[c]Insert link //[c] public func insert link ( m: text view, id: string, name: string, path: string) // cursor to the beginning of line cursor to (m, line (cursor (m)), 0, false) def t1 = new link line (new link object (id, name, path)) def t2 = new empty default line prev (t1) = nil next (t1) = t2 prev (t2) = t1 next (t2) = nil replace selection (m, t1, default cursor behavior) delete (t1) delete (t2) end //[cf] //[c] //[of]:replace selection //[c]Replaces the current selection with a string //[c] public equ replace selection (m: text view, s: string) = insert string (m, s) //[cf] //[of]:delete selection public func delete selection (m: text view) if is read only (m) return false end def tmp line = new empty default line prev (tmp line) = nil next (tmp line) = nil replace selection (m, tmp line, append cursor behavior) delete (tmp line) return true end //[cf] //[of]:delete to next word start //[c]Deletes from current position to next word start //[c] //[c]Remark: this is really a very easy implementation, //[c]it uses the regular selection and cursor commands. //[c] public func delete to next word start (m: text view) if is read only (m) return end // reset selection cursor to (m, virtual cursor view position (m), false) // select what to delete cursor word right (m, true) // delete delete selection (m) end //[cf] //[of]:delete to previous word start //[c]Deletes from current position to previous word start //[c] //[c]Remark: this is really a very easy implementation, //[c]it uses the regular selection and cursor commands. //[c] public func delete to previous word start (m: text view) if is read only (m) return end // reset selection cursor to (m, virtual cursor view position (m), false) // select what to delete cursor word left (m, true) // delete delete selection (m) end //[cf] //[c] //[of]:paste raw text //[c]Paste selection from clipboard //[c] public func paste raw text (m: text view) if is read only (m) return false end def done = false each clipboard format ? f if f == cf text done = paste text (m, f) break end end return done end //[cf] //[cf] //[of]:selecting //[of]:append selection (string buffer) //[c]Appends the current selection to the string buffer. //[c] //[c] If nothing is selected, the methods attempt to find a word //[c] under the cursor. //[c] public func append selection (m: text view, s: string buffer) def start = cursor (m) if ~ is selection empty (m) def limit = block (m) if start > limit def t = start start = limit limit = t end if text line (start) == text line (limit) append ( s, text line (start), column (start), column (limit)) return end end // no valid selection, attempt to find a word // around cursor def left := left position of word (start) def right := right position of word (start) def cl = column (left) def cr = column (right) if cr > cl append (s, text line (start), cl, cr) end end //[c] //[c]Sub-functions: //[of]: append (string buffer, text line, start, stop) //[c]Append the string //[c] func append ( s: string buffer, text line: text line, start: int, stop: int) append (s, buf (text line) + start, stop - start) end //[cf] //[c] //[cf] //[of]:select public func select (m:text view, start line: int, start column: int, stop line: int, stop column: int) cursor to (m, start line, start column, false) cursor to (m, stop line, stop column, true) end //[cf] //[of]:select word //[c]Attempts to select the word under the cursor //[c] public func select word (m: text view) def cursor = cursor (m) def ln = line number (cursor) def left := left position of word (cursor) def right := right position of word (cursor) def cl = column (left) def cr = column (right) if cr > cl select (m, ln, cl, ln, cr) end end //[cf] //[cf] //[of]:moving cursor //[of]:- Remarks - //[c]Remark: the 'move cursor' method is always invoked by the cursor //[c]commands, even if the move is not performed, in order to clear selection //[c]if shift is not set. //[cf] //[c] //[of]:cursor up public func cursor up (m: text view, shift: bool) def c := virtual cursor view position (m) sub (c, 1, c) cursor to (m, c, shift) end //[cf] //[of]:cursor down public func cursor down (m: text view, shift: bool) def c := virtual cursor view position (m) add forward (c, 1, c) cursor to (m, c, shift) end //[cf] //[of]:cursor left public func cursor left (m: text view, shift: bool) def c = cursor (m) def column = column (c) def line = line (c) if not first column (c) column -= 1 elsif not first line (c) line -= 1 column = last column of line end cursor to (m, line, column, shift) end //[cf] //[of]:cursor right public func cursor right (m: text view, shift: bool) def c = cursor (m) def column = column (c) def line = line (c) if not last column (c) column += 1 elsif not last line (c) line += 1 column = 0 end cursor to (m, line, column, shift) end //[cf] //[of]:cursor home public func cursor home (m: text view, shift: bool) def cursor = cursor (m) def column = column (cursor) def line number = line number (cursor) def buf = buf (text line (cursor)) while is blank (buf[]) ++buf end def first non blank column = buf - buf (text line (cursor)) if column == first non blank column column = 0 else column = first non blank column end cursor to (m, line number, column, shift) end //[cf] //[of]:cursor end public func cursor end (m: text view, shift: bool) cursor to (m, line number (cursor (m)), last column of line, shift) end //[cf] //[of]:cursor top public func cursor top (m: text view, shift: bool) cursor to (m, 0, 0, shift) end //[cf] //[of]:cursor bottom public func cursor bottom (m: text view, shift: bool) cursor to (m, last line of text, last column of line, shift) end //[cf] //[of]:cursor page up public func cursor page up (m: text view, shift: bool) def h = page height (m) def c := virtual cursor view position (m) def p := page view position (m) sub (c, h, c) sub (p, h, p) cursor to (m, c, p, shift) end //[cf] //[of]:cursor page down public func cursor page down (m: text view, shift: bool) def h = page height (m) def c := virtual cursor view position (m) def p := page view position (m) add forward (c, h, c) add (p, h, p) cursor to (m, c, p, shift) end //[cf] //[of]:cursor word right public func cursor word right (m: text view, shift: bool) def cursor = cursor (m) if is last column (cursor) cursor right (m, shift) return end // index used to search the beginning of the next word def buf = buf (text line (cursor)) + column (cursor) // skip current word or non-word if is word char (buf[]) while is word char (buf[]); ++buf; end else if is other (buf[]) while is other (buf[]); ++buf; end end end // skip blanks while is blank (buf[]); ++buf; end def column = buf - buf (text line (cursor)) def line number = line number (cursor) cursor to (m, line number, column, shift) end //[cf] //[of]:cursor word left public func cursor word left (m: text view, shift: bool) def cursor = cursor (m) if is first column (cursor) cursor left (m, shift) return end // start of line (limit of scan) def start = buf (text line (cursor)) : [] byte // index used to search the beginning of the word def buf = start : string + column (cursor) // skip blanks while buf > start & is blank (buf[-1]) -- buf end // skip current word or non-word if is word char (buf[-1]) while buf > start & is word char (buf[-1]) -- buf end elsif is other (buf[-1]) while buf > start & is other (buf[-1]) -- buf end end // convert index to column def column = buf : [] byte - start def line number = line number (cursor) cursor to (m, line number, column, shift) end //[cf] //[c] //[of]:scroll up public func scroll up (m: text view, shift: bool) def c := virtual cursor view position (m) def p := page view position (m) if not first line (p) sub (p, 1, p) def bottom := add (p, page height (m) - 1) if is below (c, bottom) set line (c, bottom) end end cursor to (m, c, p, shift) end //[cf] //[of]:scroll down public func scroll down(m: text view, shift: bool) def c := virtual cursor view position (m) def p := page view position (m) if not last line (p) add (p, 1, p) if is above (c, p) set line (c, p) end end cursor to (m, c, p, shift) end //[cf] //[c] //[of]:first folder or link //[c]First folder or Link //[c] public func first headline (m: text view) def c = cursor (m) def line number = line number (c) def text line = text line (c) while not nil (text line) if is headline (text line) def pos := text view position (m, line number, cursor x (m)) cursor to (m, pos, false) break end line number += 1 text line = next (text line) end end //[cf] //[of]:next folder or link //[c]Next Folder or Link //[c] public func next headline (m: text view) def c = cursor (m) def line number = line number (c) def text line = text line (c) repeat line number += 1 text line = next (text line) if is nil (text line) break end if is headline (text line) def pos := text view position (m, line number, cursor x (m)) cursor to (m, pos, false) break end end end //[cf] //[of]:prev folder or link //[c]Previous Folder or Link //[c] public func prev headline (m: text view) def c = cursor (m) def line number = line number (c) def text line = text line (c) repeat line number -= 1 text line = prev (text line) if is nil (text line) break end if is headline (text line) def pos := text view position (m, line number, cursor x (m)) cursor to (m, pos, false) break end end end //[cf] //[c] //[c]With page adjustment: //[of]:cursor to (position, shift) //[c]Moves the cursor to a graphical location. //[c] //[c]The page is automatically adjusted to keep the cursor visible. //[c] public func cursor to ( m: text view, pos: text view position, shift: bool) move cursor (m, pos, shift) adjust page (m) end //[cf] //[of]:cursor to (position, page, shift) //[c]Moves the cursor to a graphical location and moves the page. //[c] public func cursor to ( m: text view, pos: text view position, page: text view position, shift: bool) move cursor (m, pos, shift) adjust x (m, page) move page (m, page) end //[cf] //[of]:cursor to (line, column, shift) //[c]Moves the cursor to a text object location. //[c] //[c]The page is automatically adjusted to keep the cursor visible. //[c] public func cursor to ( m: text view, cursor line: int, cursor column: int, shift: bool) move cursor (m, cursor line, cursor column, shift) adjust page (m) end //[cf] //[c] //[c]Without page adjustment: //[of]:move cursor (position, shift) //[c]Moves the cursor to a new location //[c] public func move cursor ( m: text view, pos: text view position, shift: bool) def line number = line number (pos) def line object = text line (m, line number) def subline = subline (pos) def x = x (pos) def column = column (m, line object, subline, x) move cursor ( m, line number, column, true, shift) end //[cf] //[of]:move cursor (line, column, shift) //[c]Moves the cursor to a new location //[c] //[c] This is the primitive method for cursor moves: all cursor operations //[c] invoke this method. //[c] //[c] Arguments: //[c] - cursor line: the new line (-1 for last line of text) //[c] - cursor column: the new column (-1 to keep x) //[c] - page line: the first line of the page or -1 for auto-center //[c] - shift: select mode ? //[c] public func move cursor (m: text view, cursor line: int, cursor column: int, shift: bool) move cursor ( m, cursor line, cursor column, false, shift) end //[cf] //[of]:move cursor (line, column, virtual x, shift) //[c]Moves the cursor to a new location //[c] //[c] This is the primitive method for cursor moves: all cursor operations //[c] invoke this method. //[c] //[c] Arguments: //[c] - cursor line: the new line (-1 for last line of text) //[c] - cursor column: the new column (-1 to keep x) //[c] - page line: the first line of the page or -1 for auto-center //[c] - shift: select mode ? //[c] public func move cursor (m: text view, cursor line: int, cursor column: int, virtual x: bool, shift: bool) def cursor = cursor (m) def old cursor column = column (cursor) def old cursor line = line (cursor) // Update the line information first if cursor line == last line of text cursor line = size (text (m)) - 1 end text line (cursor) = text line (m, cursor line) // Order is line (cursor) = cursor line // important ! // out of bound: correct to last line if is nil (text line (cursor)) cursor line = size (text (m)) - 1 text line (cursor) = text line (m, cursor line) // Order is line (cursor) = cursor line // important ! end // real x of the cursor (used to center frame later) def cursor x: int // Update the column information if cursor column == last column of line cursor column = size (text line (cursor)) : int end column (cursor) = cursor column cursor x = x (m, text line (cursor), cursor column) if ~ virtual x cursor x (m) = cursor x end // Process the selection if ~ shift // invalidate all impacted lines (selection may be cleared) // 1. invalidate the previous selection invalidate (m, line (block (m)), old cursor line) // 2. invalidate the current line for cursor invalidate (m, line (cursor)) set (block (m), cursor) else // invalidate all impacted lines (new selection) invalidate (m, old cursor line, line (cursor)) end // force the cursor to be visible show cursor (m) if ( old cursor column <> column (cursor) || old cursor line <> line (cursor) ) // if the widget has a non empty selection and has the focus: // take the control of the primary selection if ~ is selection empty (m) && has focus (m) take primary selection (m) end // Send notification on selection changed (m) end end //[cf] //[cf] //[of]:scrolling //[of]:adjust page //[c]Forces cursor to be in the page. //[c] //[c]Remark: move page is always invoked since it can adjust the page //[c]horizontally. //[c] public func adjust page (m: text view) if height (client rect (m)) == 0 page command (m) = pc adjust return end def page := page view position (m) def cursor := cursor view position (m) if is above (cursor, page) set line (page, cursor) else def visible lines = page height (m) def bottom := add (page, visible lines - 1) if visible lines > 0 && is below (cursor, bottom) set line (page, sub (cursor, visible lines - 1)) end end adjust x (m, page) move page (m, page) end //[cf] //[of]:center page //[c]Forces cursor to be in the middle of page if it is not visible //[c]within the current page. //[c] //[c]Remark: move page is always invoked since it can adjust the page //[c]horizontally. //[c] public func center page (m: text view) if height (client rect (m)) == 0 page command (m) = pc center return end def page := page view position (m) def cursor := cursor view position (m) def visible lines = page height (m) def bottom := add (page, visible lines - 1) if is above (cursor, page) || is below (cursor, bottom) set line (page, sub (cursor, visible lines / 2)) end adjust x (m, page) move page (m, page) end //[cf] //[of]:adjust x (position) //[c]Adjust the x coordinate of the position to have cursor inside limits //[c] public func adjust x (m: text view, position: text view position) def page := page view position (m) def cursor := cursor view position (m) def cursor x = x (cursor) def page x = x (page) if cursor x < page x page x = max (0, cursor x - margin (m)) else def page w = width (client rect (m)) if cursor x >= page x + page w page x = cursor x - page w + margin (m) end end x (position) = page x end //[cf] //[of]:move page (position) //[c]Moves the page vertically //[c] public func move page (m: text view, position: text view position) def page line = line number (position) def page subline = subline (position) def page := page view position (m) def cursor := cursor view position (m) def moved = false if page line <> line number (page) || page subline <> subline (page) def line = text line (m, page line) // out of bound: correct to last line if is nil (line) page line = size (text (m)) - 1 page subline = number of sublines (m, page line) line = text line (m, page line) end line number (page (m)) = page line subline (page (m)) = page subline text line (page (m)) = line moved = true end // Move the frame horizontally def page x = x (position) if page x <> x (page) x (page (m)) = page x moved = true end if moved invalidate (m) update pointer (m) on page changed (m) end end //[cf] //[cf] //[of]:finding //[c]Finds a line similar to the current one in the given folder //[c] //[c]Returns the line number of the similar line or -1 if no line is found. //[c]The comparison applies only to headlines (title of links or folders). //[c] public func find similar line (view: text view, folder: text object) def line = text line (cursor (view)) if is headline (line) def headline = hyper text object (line) def title = title (headline) def line number = 0 each line (folder) ? l if is headline (l) && is equal (title (hyper text object (l)), title) return line number end line number += 1 end end return -1 end //[cf] //[of]:enumerating //[of]:each line of selection //[c]Enumerates all lines of the selection //[c] //[c] If the selection is empty, only the current line is processed. //[c] public equ each line of selection (m: text view) def pos 1 = min (cursor (m), block (m)) def pos 2 = max (cursor (m), block (m)) def line = text line (pos 1) def limit = text line (pos 2) def line number = line number (pos 1) while line <> limit // Get next before yielding // The line can be deleted on a modification def next = next (line) yield (line, line number) line = next ++ line number end end //[cf] //[cf] //[of]:accessing //[of]:text position under point //[c]Returns the Position in Text under the Given Point //[c] //[c]If the point is over the end of the text, the last line is used instead. //[c] public func text position under point (m: text view, pt: point, return pos: text position) def page := page view position (m) // Get Line add (page, y (pt) / font height (m), page) def line number = line number (page) def line object = text line (m, line number) // Get Column def subline = subline (page) def x = x (pt) + x (page) def column = column (m, line object, subline, x) // Setup returned value column (pos) = column line number (pos) = line number text line (pos) = line object end //[cf] //[c] //[of]:set folder (folder) //[c]Changes the Current Edited Folder //[c] public func set folder (m: text view, text: text object) def corner := top left position (text) def page := top left page position (text) set folder (m, text, page, corner, corner) end //[cf] //[of]:set folder (folder, page, cursor, block) //[c]Set the current folder and specify position of page, cursor and block //[c] //[c]Important: the format must be set first //[c] public func set folder ( m: text view, text: text object, page: page position, cursor: text position, block: text position) if not nil (text (m)) remove listener (text (m), text listener (m)) end // cancel select mode if any set edit mode (m) text (m) = text set (page (m), page) set (cursor (m), cursor) set (block (m), block) // reset ranges valid ranges (m) = false // clear highlight highlight line (m) = nil // listen events from the text object add listener (text (m), text listener (m)) reset sublines (m) invalidate min size (m) invalidate (m) update pointer (m) // send a 'value changed' event // since the content changes, it is equivalent to a new value on value changed (m) // send a 'selection changed' event on selection changed (m) // send a 'page changed' event on page changed (m) end //[cf] //[of]:set format (text view format) //[c]Changes the Format //[c] public func set format (m: text view, f: text view format) format (m) = f word wrap (m) = word wrap (f) reset font height (m) invalidate min size (m) invalidate (m) end //[cf] //[of]:set structured text clipboard format //[c] public func set structured text clipboard format ( m: text view, f: clipboard format) cf structured text (m) = f // This method is invoked only once, so the registration is performed // only once. add primary selection format (m, cf structured text (m)) end //[cf] //[of]:reset format //[c]Resets the Format //[c] public func reset format (m: text view, f: text view format) def need update = false if format (m) <> nil def old = word wrap (m) def new = word wrap (f) need update = (old <> new) word wrap (m) = new end format (m) = f reset font height (m) if need update reset sublines and page (m) end invalidate min size (m) invalidate (m) update pointer (m) // state need to be updated def text = text (m) undefined state (text) = 0 undefined start (text) = 0 undefined stop (text) = count (text) // the font may have changed => the page size may have changed on page changed (m) end //[cf] //[c] //[of]:set append mode //[c]Switch to Append Mode //[c] //[c]The cursor will be moved at the end of any change occuring //[c]on the current edited folder. //[c] public func set append mode (m: text view) cursor behavior (m) = append cursor behavior end //[cf] //[of]:set ignore mode //[c]Switch to Ignore Mode //[c] //[c]The cursor will ignore any change occuring on the current edited folder. //[c] public func set ignore mode (m: text view) cursor behavior (m) = default cursor behavior end //[cf] //[of]:set highlight line //[c]Sets the Line to Be Temporarily Highlighted //[c] //[c]Takes effect only on link and folder lines. //[c]Usually used when selecting a line for contextual menu. //[c] public func set highlight line (m: text view, line: text line) highlight line (m) = line invalidate (m) end //[cf] //[of]:set cursor mode //[c]Changes the cursor mode //[c] public func set cursor mode (m: text view, overwrite: bool) if overwrite mode (m) == overwrite return end overwrite mode (m) = overwrite show cursor (m) on cursor mode (m) end //[cf] //[c] //[of]:page height //[c]Gets page height (number of entire lines) //[c] public func page height (m: text view) return height (client rect (m)) / font height (m) end //[cf] //[of]:number of sublines //[c]Returns the total number of sublines //[c] //[c]If the word wrapping is not active, this value is identical to the total //[c]number of lines. //[c] public func number of sublines (m: text view) if word wrap (format (m)) // word wrap is active: returns the previously counted sublines return sublines count (m) else // word wrap is not active: return the number of lines return size (text (m)) end end //[cf] //[of]:cursor view position //[c]Returns the view position of the cursor //[c] public func cursor view position (m: text view, return pos: text view position) def cursor = cursor (m) def point := text point (m, text line (cursor), column (cursor)) initialize ( pos, m, line number (cursor), subline (point), x (point)) end //[cf] //[of]:virtual cursor view position //[c]Returns the view position of the virtual cursor //[c] //[c]The virtual cursor is same as cursor except that x can be over //[c]the end of the line. //[c] public func virtual cursor view position (m: text view, return pos: text view position) def cursor = cursor (m) def point := text point (m, text line (cursor), column (cursor)) initialize ( pos, m, line number (cursor), subline (point), cursor x (m)) end //[cf] //[of]:page view position //[c]Returns the view position of the page //[c] public func page view position (m: text view, return pos: text view position) def page = page (m) initialize ( pos, m, line number (page), subline (page), x (page)) end //[cf] //[of]:text view position //[c]Returns a view position //[c] public func text view position (m: text view, line number: int, x: int, return pos: text view position) initialize ( pos, m, line number, 0, x) end //[cf] //[cf] //[of]:testing //[of]:is selection empty //[c]Returns true if the current selection is empty //[c] public func is selection empty (m: text view) return block (m) == cursor (m) end //[cf] //[of]:is overwrite //[c]Returns true if the cursor mode is 'overwrite' //[c] public func is overwrite (m: text view) return overwrite mode (m) end //[cf] //[c] //[of]:has first folder or link //[c]Tests if there is a link or a folder below or on the current line //[c] public func has first headline (m: text view) def c = cursor (m) def text line = text line (c) while not nil (text line) if is headline (text line) return true end text line = next (text line) end return false end //[cf] //[of]:has next folder or link //[c]Tests if there is a link or a folder below the current line //[c] public func has next headline (m: text view) def c = cursor (m) def text line = next (text line (c)) while not nil (text line) if is headline (text line) return true end text line = next (text line) end return false end //[cf] //[of]:has prev folder or link //[c]Tests if there is a link or a folder above the current line //[c] public func has prev headline (m: text view) def c = cursor (m) def text line = text line (c) text line = prev (text line) while not nil (text line) if is headline (text line) return true end text line = prev (text line) end return false end //[cf] //[c] //[of]:can paste raw text //[c]Can paste raw text ? //[c] public func can paste raw text (m: text view) if is read only (m) return false end def can paste = false each clipboard format ? f if f == cf text // do not return: the iterator must close the clipboard can paste = true break end end return can paste end //[cf] //[cf] //[of]:converting //[of]:as text view //[c]Get the text view interface of a box //[c] public equ as text view (box: box) = get interface (box, text view interface) : text view //[cf] //[of]:convert line to logical line //[c]Converts line number + subline offset to a logical line (line number with wrapping) //[c] public func convert line to logical line (m: text view, line number: int, subline: int) if ~ word wrap (format (m)) return line number end def sum = 0 def p = sublines (m) def i = line number - 1 while i >= 0 sum += p [i] : int i -= 1 end return sum + subline end //[cf] //[cf] //[c] //[c]restricted: //[of]:class accessing //[of]:text view class public func text view class def c = the text view class if ~ initialized initialized = true // initialize with parent class copy (c, container box class) // patch some methods mem size (c) = sizeof local text view class release (c) = ^actual release (text view) get interface (c) = ^actual get interface (text view, interface id) set read only (c) = ^actual set read only (text view, bool) on size (c) = ^handle size (text view) compute min size (c) = ^actual compute min size (text view) on paint (c) = ^handle paint (text view, canvas, rectangle) on focus (c) = ^handle focus (text view, event) on blur (c) = ^handle blur (text view, event) on enter menu (c) = ^handle enter menu (text view, event) on leave menu (c) = ^handle leave menu (text view, event) on key down (c) = ^handle key down (text view, key event) on char (c) = ^handle char (text view, char event) on mouse down (c) = ^handle mouse down (text view, mouse button event) on mouse up (c) = ^handle mouse up (text view, mouse button event) on mouse move (c) = ^handle mouse move (text view, mouse move event) on mouse wheel (c) = ^handle mouse wheel (text view, mouse wheel event) on mouse cancel (c) = ^handle mouse cancel (text view, event) on mouse double click (c) = ^handle mouse double click (text view, mouse button event) on receive primary selection (c) = ^handle receive primary selection (text view, receive primary selection event) on query primary selection (c) = ^handle query primary selection (text view, query primary selection event) accept focus (c) = ^yes (box) copy (c) = ^actual copy (text view) paste (c) = ^actual paste (text view) clear (c) = ^delete selection (text view) select all (c) = ^actual select all (text view) can copy (c) = ^actual can copy (text view) can paste (c) = ^actual can paste (text view) can clear (c) = ^actual can clear (text view) can select all (c) = ^yes (box) on enter (c) = ^notify parent (box, event) on leave (c) = ^notify parent (box, event) on page changed (c) = ^notify parent (box, event) on cursor mode (c) = ^notify parent (box, event) end return c end //[c] private def initialized = false private def the text view class: local text view class //[cf] //[cf] //[c] //[of]:initialize - release //[of]:initialize public func initialize (m: text view, parent: box) initialize (super (m), parent) class (m) = text view class text (m) = nil format (m) = nil word wrap (m) = false cursor behavior (m) = default cursor behavior has focus (m) = false overwrite mode (m) = false mouse mode (m) = tv edit mode read only (m) = false blinking state (m) = true initialize (cursor timer (m), 500, m, ^handle cursor timer (text view, timer)) initialize (autoscroll timer (m), 250, m, ^handle autoscroll timer (text view, timer)) highlight line (m) = nil initialize (sublines (m)) page command (m) = pc none ranges (m) = nil valid ranges (m) = false initialize (ibeam pointer (m), cursor ibeam) initialize (hand pointer (m), cursor hand) recipient (text listener (m)) = m text changed (text listener (m)) = ^handle replace event (text view, text object, replace text event) prop changed (text listener (m)) = ^handle prop changed (text view, text object, replace prop event) group changed (text listener (m)) = ^ignore group event (object, text object, bool) destroyed (text listener (m)) = ^handle destroyed (text view, text object) marker added (text listener (m)) = ^handle marker added (text view, text object, text marker) marker removed (text listener (m)) = ^handle marker removed (text view, text object, text marker) set cursor (m, ibeam pointer (m)) // Declare that this widget can receive any primary selection in text // format. The structured text format is declared later when calling // 'set structured text clipboard format'. add primary selection format (m, cf text) end //[cf] //[cf] //[of]:box interface implementation //[of]:actual can copy //[c]Test if copy available //[c] public func actual can copy (m: text view) return ~ is selection empty (m) end //[cf] //[of]:actual can paste //[c]Can paste ? //[c] public func actual can paste (m: text view) if is read only (m) return false end def can paste = false each clipboard format ? f if f == cf text || f == cf structured text (m) // do not return: the iterator must close the clipboard can paste = true break end end return can paste end //[cf] //[of]:actual can clear //[c]Can clear selection ? //[c] public func actual can clear (m: text view) return ~ is read only (m) && ~ is selection empty (m) end //[cf] //[of]:actual copy //[c]Copy selection to clipboard //[c] //[c]Performance note: //[c]The method costs a lot of memory, the selection is first copied in a list //[c]of lines, then is is copied in a text buffer, and finally this buffer is put is //[c]the clipboard. The memory will have 4 copies of the selection in memory ! //[c] //[c]Sub-functions: //[of]:copy lines (text line, string buffer, inner) //[c]Copy lines recursively in the string buffer //[c] //[c] This function copies the structured text as raw text. //[c] All structure directives will be lost. //[c] func copy lines (m: text view, line: text line, t: string buffer) : void def f = format (m) def prefix = open comment (f) def suffix = close comment (f) copy lines (line, t, false, prefix, suffix) end //[c] func copy lines ( line: text line, t: string buffer, inner: bool, prefix: string, suffix: string) : void while not nil (line) if is folder (line) t << prefix << \w << display text (line) << suffix << new line sequence def folder = text object (line) copy lines (first line (folder), t, true, prefix, suffix) elsif is link (line) t << prefix << \w << display text (line) << suffix << new line sequence elsif is comment (line) t << prefix << \w << buf (line) << suffix if inner || not last line (line) t << new line sequence end else t << buf (line) if inner || not last line (line) t << new line sequence end end line = next (line) end end //[cf] //[c] public func actual copy (m: text view) // the method has no effect if the line is empty if is selection empty (m) return false end // Put the selection in a list of lines def lines := list of lines (cursor (m), block (m)) set clipboard content ? // Push structured text def b := memory buffer serialize (lines, b) set clipboard data (cf structured text (m), base (b), size (b)) release (b) // Push raw text def t := temp string buffer copy lines (m, first (lines), t) set clipboard data (cf text, base (t) : [] byte, size (t)) release (t) end delete lines (lines) return true end //[cf] //[of]:actual paste //[c]Paste selection from clipboard //[c] public func actual paste (m: text view) def done = false each clipboard format ? f if f == cf text done = paste text (m, f) break elsif f == cf structured text (m) && can structure (format (m)) done = paste structured text (m, f) break end end return done end //[cf] //[of]:actual select all func actual select all (m: text view) select (m, last line of text, last column of line, 0, 0) return true end //[cf] //[c] //[of]:handle receive primary selection //[c]I've requested the content of the primary selection and I'm just receiving it. //[c] public func handle receive primary selection (m: text view, e: receive primary selection event) def cf = format (e) def buffer = buffer (e) def size = size (e) // no data available if is nil (buffer) if cf == cf structured text (m) // no structured text available: query text query primary selection (m, cf text) end return end if cf == cf text receive text (m, buffer, size) elsif cf == cf structured text (m) && can structure (format (m)) receive structured text (m, buffer, size) end end //[cf] //[of]:handle query primary selection //[c]I am the owner of the primary selection and another application is asking me //[c]the selection. //[c] //[c]Depending on the requested format, I send selection using //[c]'send primary selection'. //[c] public func handle query primary selection ( m: text view, e: query primary selection event) // the method has no effect if the selection is empty if is selection empty (m) return end // Put the selection in a list of lines def lines := list of lines (cursor (m), block (m)) def cf = format (e) if cf == cf text // Copy the list of lines into the string buffer def t := temp string buffer copy lines (m, first (lines), t) send primary selection (m, e, base (t) : [] byte, size (t)) release (t) elsif cf == cf structured text (m) // Copy the list of lines into the memory buffer def b := memory buffer serialize (lines, b) send primary selection (m, e, base (b), size (b)) release (b) end delete lines (lines) end //[cf] //[c] //[of]:handle size //[c]Must repaint all when size changed //[c] public func handle size (m: text view) if not nil (text (m)) reset sublines and page (m) end invalidate (m) def pc = page command (m) page command (m) = pc none if pc == pc adjust adjust page (m) elsif pc == pc center center page (m) end end //[cf] //[c] //[of]:handle mouse down //[c]On Mouse Down //[c] public func handle mouse down (m: text view, e: mouse button event) if button (e) == left mouse button def shift = (status (e) & SHIFT) <> 0 move cursor with autoscroll (m, position (e), shift) set focus (m) set select mode (m) return true elsif button (e) == middle mouse button x (query point (m)) = x (position (e)) y (query point (m)) = y (position (e)) def f = can structure (format (m)) -> cf structured text (m), cf text query primary selection (m, f) return true else // default handling return handle mouse down (super (m), e) end end //[cf] //[of]:handle mouse up public func handle mouse up (m: text view, e: mouse button event) if button (e) == left mouse button set edit mode (m) return true else // default handling return handle mouse up (super (m), e) end end //[cf] //[of]:handle mouse move public func handle mouse move (m: text view, e: mouse move event) update pointer (m) if mouse mode (m) == tv select mode move cursor with autoscroll (m, position (e), true) return true else // default handling return handle mouse move (super (m), e) end end //[cf] //[of]:handle mouse wheel public func handle mouse wheel (m: text view, e: mouse wheel event) equ lines per mouse wheel unit = 4 def delta = delta (e) * lines per mouse wheel unit move page (m, sub (page view position (m), delta)) return true end //[cf] //[of]:handle mouse double click public func handle mouse double click (m: text view, e: mouse button event) if button (e) == left mouse button def cursor = cursor (m) def text line = text line (cursor) if is headline (text line) on enter (m) else select word (m) end return true end return handle mouse double click (super (m), e) end //[cf] //[of]:handle mouse cancel public func handle mouse cancel (m: text view, e: event) // cancel select mode if necessary set edit mode (m) return true end //[cf] //[c] //[of]:handle focus public func handle focus (m: text view, e: event) // remember that the box has the focus has focus (m) = true // force the cursor to be visible and start blinking show cursor (m) // the selection need to be refreshed invalidate selection (m) return handle focus (super (m), e) end //[cf] //[of]:handle blur public func handle blur (m: text view, e: event) handle blur (super (m), e) has focus (m) = false // the selection need to be refreshed invalidate selection (m) return handle blur (super (m), e) end //[cf] //[of]:handle enter menu public func handle enter menu (m: text view, e: event) handle enter menu (super (m), e) has focus (m) = false // the selection need to be refreshed invalidate selection (m) return handle enter menu (super (m), e) end //[cf] //[of]:handle leave menu //[c]The user has just exit the menu loop //[c] //[c]It behaves exactly as if the widget gains the focus. //[c] public func handle leave menu (m: text view, e: event) // default handling handle leave menu (super (m), e) // remember that the box has the focus has focus (m) = true // force the cursor to be visible and start blinking show cursor (m) // the selection need to be refreshed invalidate selection (m) return handle leave menu (super (m), e) end //[cf] //[of]:handle key down public func handle key down (m: text view, e: key event) def status = status (e) def key = key (e) def shift = (status & SHIFT) <> 0 def status without shift = status & ~ SHIFT if key==VK DOWN && status == ALT next headline (m) elsif key==VK UP && status == ALT prev headline (m) elsif key==VK DOWN && status == CTRL scroll down (m, false) elsif key==VK UP && status == CTRL scroll up (m, false) elsif key==VK RIGHT && status without shift == CTRL cursor word right (m, shift) elsif key==VK LEFT && status without shift == CTRL cursor word left (m, shift) elsif key==VK UP && status without shift == 0 cursor up (m, shift) elsif key==VK DOWN && status without shift == 0 cursor down (m, shift) elsif key==VK RIGHT && status without shift == 0 cursor right (m, shift) elsif key==VK LEFT && status without shift == 0 cursor left (m, shift) elsif key==VK HOME && status without shift == CTRL cursor top (m, shift) elsif key==VK END && status without shift == CTRL cursor bottom (m, shift) elsif key==VK HOME && status without shift == 0 cursor home (m, shift) elsif key==VK END && status without shift == 0 cursor end (m, shift) elsif key==VK PRIOR && status without shift == 0 cursor page up (m, shift) elsif key==VK NEXT && status without shift == 0 cursor page down (m, shift) elsif key==VK INSERT && status == 0 set cursor mode (m, ~ overwrite mode (m)) else if is read only (m) return handle key down (super (m), e) end if key==VK DELETE && status == 0 if is selection empty (m) delete forward char (m) else delete selection (m) end elsif key==VK BACK && status without shift == 0 if is selection empty (m) if can unindent (m) delete indentation (m) else delete backward char (m) end else delete selection (m) end elsif key==VK DELETE && status == CTRL delete to next word start (m) elsif key==VK BACK && status == CTRL delete to previous word start (m) elsif key==VK RETURN && status == 0 insert new line (m) elsif key==VK TAB && status without shift == 0 insert tab (m, shift) else return handle key down (super (m), e) end end return true end //[cf] //[of]:handle char public func handle char (m: text view, e: char event) if ~ is read only (m) def c = char code (e) // convert to a byte since a char can be signed if c:byte >= 32:b && c:b <> 127:b && (status (e) & (ALT|CTRL)) <> ALT insert char (m, c) return true end end return handle char (super (m), e) end //[cf] //[c] //[of]:handle paint public func handle paint (m: text view, c: canvas, clip: rectangle) // if GetKeyState(VK SHIFT)<0s then // fill rectangle (c, clip, 0xFF0000:color) // return // end // the ranges must be validated now validate ranges (m) def ps: local paint line current font (ps) = nil canvas (ps) = c width (ps) = width (clip) tx (ps) = left (clip) def style = style (m) def f = format (m) def font height = font height (m) def first line = top (clip) / font height def last line = (bottom (clip) - 1) / font height // reach line def page = page (m) def y = - subline (page) def line = text line (page) def line number = line number (page) def colorizer = colorizer (f) while not nil (line) def h = number of sublines (m, line number) if y + h > first line break end y += h line = next (line) line number += 1 end ty (ps) = y * font height text line (ps) = line line (ps) = line number count (ps) = last line + 1 - y // find the first range that end strictly after the beginning // of the clipping area ranges (ps) = ranges (m) range index (ps) = range index (ranges (m), line number) marker colors (ps) = marker colors (f) // display lines validate to (m, line number + count (ps)) if cursor (m) < block (m) block start (ps) = cursor (m) block limit (ps) = block (m) else block start (ps) = block (m) block limit (ps) = cursor (m) end while count (ps) > 0 && not nil (text line (ps)) dump line (m, ps) text line (ps) = next (text line (ps)) line (ps) += 1 end // display end area (if visible) if ty (ps) < bottom (clip) def r := rectangle ( left (clip), ty (ps), width (clip), bottom (clip) - ty (ps)) fill rectangle (c, r, comment back color (f)) end end //[c] //[of]:validate to //[c]Validate state of line up to the 'range stop' line (excluded) //[c] func validate to (m: text view, range stop: int) def text = text (m) def colorizer = colorizer (format (m)) // dans la zone valide if range stop <= undefined start (text) return end def state = undefined state (text) def line number = undefined start (text) def line = text line (m, line number) def undefined stop = undefined stop (text) while not nil (line) && line number < undefined stop && line number < range stop state (line) = state : word if is text (line) state = colorize (colorizer, buf (line), state, nil) end line = next (line) line number += 1 end while not nil (line) && line number < range stop if state (line) == state : word undefined state (text) = state undefined start (text) = count (text) undefined stop (text) = count (text) return end state (line) = state : word if is text (line) state = colorize (colorizer, buf (line), state, nil) end line = next (line) line number += 1 end undefined start (text) = line number undefined state (text) = state end //[cf] //[of]:range index func range index (ranges: vector, line: int) if not nil (ranges) def i = unsigned (0) def n = size (ranges) while i < n def range = ranges [i] : text view range def limit = limit (range) if line (limit) >= line return i end i += 1 end end return unsigned (-1) end //[cf] //[cf] //[c] //[of]:actual compute min size public func actual compute min size (m: text view) def min size = m.min size width (min size) = default min width height (min size) = default min height end //[cf] //[of]:actual get interface //[c]Get an interface //[c] public func actual get interface (m: text view, id: interface id) if id == text view interface return m end return actual get interface (super (m), id) end //[c] private equ text view interface = the text view class //[cf] //[c] //[of]:actual set read only public func actual set read only (m: text view, ro: bool) if read only (m) == ro return end read only (m) = ro end //[cf] //[of]:actual release public func actual release (m: text view) def text = text (m) if not nil (text) remove listener (text, text listener (m)) text (m) = nil end def ranges = ranges (m) if not nil (ranges) each (ranges) ? range delete (range: text view range) end delete (ranges) end release (sublines (m)) release (cursor timer (m)) release (autoscroll timer (m)) release (ibeam pointer (m)) release (hand pointer (m)) actual release (super (m)) end //[cf] //[cf] //[c] //[c]private: //[of]:constants //[c]Selection behavior //[c] //[c] This variable tells how to update selection when //[c] receiving a replace text event. //[c] //[c] default: it is an external update, just correct position //[c] //[c] select: block is moved to the beginning of the inserted text and //[c] cursor is moved to the end. //[c] //[c] append: block and cursor are moved to the end of the inserted text. //[c] enum selection behavior default cursor behavior select cursor behavior append cursor behavior end //[c] //[c]The default min size //[c] equ default min width = 100 equ default min height = 100 //[c] //[c]Maximum number of color ranges in a single line //[c] equ max length = 8192 //[c] //[c]Maximum length of a range //[c] equ max range length = 8192 //[c] //[c]Maximum length of a sequence //[c]If a sequence is longer, it is cut in several subsequences. This prevents //[c]to pass a sequence too long to the graphics context routine //[c] equ max sequence = 256 //[cf] //[c] //[of]private-painting:painting //[of]paint-line:paint line structure //[c]paint line //[c] //[c]tx is the x coordinate in the window //[c]ty is the y coordinate in the window //[c]width is the visible width to display starting to tx //[c] struct paint line // number of lines (sublines) still to paint count: int current font: font canvas: canvas text line: text line line: int tx: int ty: int width: int block start: text position block limit: text position // temporary storage back color: color ranges: vector range index: dword marker colors: [] color end //[cf] //[c] //[of]:dump line //[c]Paint one line //[c] func dump line (m: text view, ps: paint line) // save ty def ty = ty (ps) def font = font (m, text line (ps)) if font <> current font (ps) current font (ps) = font set font (canvas (ps), font) end def text height = height (canvas (ps)) def line height = font height (m) // Avoid dynamic memory allocation for highlight buffer // but when the line is too long, we must use dynamic // allocation. def highlight buffer: [max length+1] local char color def range buffer: [max range length+1] char // buffer to store a sequence def highlight = highlight buffer def buf = range buffer def line size = size (text line (ps)) if line size > max length highlight = allocate memory ((line size+1) * sizeof local char color) : [] local char color buf = allocate memory ((line size+1) * sizeof char) : [] char end def src = colorize line (m, ps, highlight) def p = src // limit of the visible area in in the window def right = tx (ps) + width (ps) def q = buf // dump the line def cc = highlight def limit = cc + size (src):int def count = number of sublines (m, line (ps)) while count > 0 def current fore color = cc[].fore color def current back color = cc[].back color // Limit of current subline def subline limit = next offset (m, p, canvas (ps)) // x scans the line from the beginning in window coordinates def x = - x (page (m)) // next tab position in same coordinate system as x def tab size = tab size in pixels (m, canvas (ps)) def next tab = x + tab size // this rectangle is used to fill tabs and end of line def r := rectangle (0, ty (ps), 0, line height) // this rectangle is used to fill blank above a word if line height is greater than // text height def r2 := rectangle (0, ty (ps) + text height, 0, line height) while x < right && p:[]byte < subline limit:[]byte def c = p++[] ++cc if c == nul char break end if c <> \t q++[] = c end // Dump sequence if c==\t || p:[]byte==subline limit:[]byte || cc==limit || (current fore color <> cc[].fore color) || (current back color <> cc[].back color) || (q - buf > max sequence) if q - buf > 0 // width occupied by the text def w = text width (canvas (ps), buf, q - buf) if x+w > tx (ps) set back color (canvas (ps), current back color) set text color (canvas (ps), current fore color) text out (canvas (ps), x, ty (ps), w, text height, buf, q - buf) if line height > text height x (r2) = x w (r2) = w fill rectangle (canvas (ps), r2, current back color) end end // advance x x += w // reset buf pointer q = buf end if c==\t while next tab <= x next tab += tab size end left (r) = x width (r) = next tab - x fill rectangle (canvas (ps), r, current back color) // advance x x = next tab end current fore color = cc[].fore color current back color = cc[].back color end end // Fill the rectangle if the width is > 0 if x < right def left = max (0, x) left (r) = left width (r) = right - left // on wrapped line, repeat color of previous char def back color = back color (cc [(count==1) -> 0, -1]) fill rectangle (canvas (ps), r, back color) end // Not the last subline: need to advance to next subline // (and advance color pointer) cc += subline limit - p p = subline limit ty (ps) += line height count (ps) -= 1 count -= 1 end // compute cursor rectangle in window coordinate def cursor line = text line (cursor (m)) if cursor line == text line (ps) && has focus (m) && blinking state (m) // find best color for cursor def c = back color (ps) def red = red (c) : dword def green = green (c) : dword def blue = blue (c) : dword if (red + green + blue) > 3 * 128 c = 0 : color else c = 0xDFEFFF : color end def pt := text point (m, cursor line, column (cursor (m))) def r : local rectangle if is overwrite (m) set (r, x (pt) - x (page (m)), ty + (subline (pt) + 1) * line height - caret width (format (m)), ave char width (canvas (ps)), caret width (format (m))) else set (r, x (pt) - x (page (m)), ty + subline (pt) * line height, caret width (format (m)), line height) end fill rectangle (canvas (ps), r, c) end if highlight <> highlight buffer free memory (buf) free memory (highlight) end end //[c] //[c]Sub-functions: //[of]:colorize line //[c]Colorizes the line and returns a pointer to the string to display //[c] func colorize line (m: text view, ps: paint line, highlight: [] local char color) def line = text line (ps) def src = buf (line) def f = format (m) def back color : color switch type (line) case lt text // compute syntax highlighting def colorizer = colorizer (f) colorize (colorizer, src, state (line) : int, highlight) if ~ has focus (m) && text line (cursor (m)) == line && is selection empty (m) back color = inactive back color (f) else back color = text back color (f) end case lt comment colorize mono (src, highlight, comment fore color (f)) if ~ has focus (m) && text line (cursor (m)) == line && is selection empty (m) back color = inactive back color (f) else back color = comment back color (f) end case lt folder src = title (text object (line)) def fore color : color if (text line (cursor (m)) == line && is selection empty (m)) || line == highlight line (m) if has focus (m) fore color = highlight fore color (f) back color = highlight back color (f) else fore color = folder fore color (f) back color = inactive back color (f) end else fore color = folder fore color (f) back color = folder back color (f) end colorize mono (src, highlight, fore color) case lt link src = title (link object (line)) def fore color : color if (text line (cursor (m)) == line && is selection empty (m)) || line == highlight line (m) if has focus (m) fore color = highlight fore color (f) back color = highlight back color (f) else fore color = link fore color (f) back color = inactive back color (f) end else fore color = link fore color (f) back color = link back color (f) end colorize mono (src, highlight, fore color) end // apply selection to the background apply selection (ps, highlight, back color, f) // save back color (used to determine cursor color) back color (ps) = back color return src end //[c] //[c]Sub-functions //[of]:apply selection //[c]Apply selection - colorize the background of one line according to selection //[c] func apply selection ( ps: paint line, highlight: [] local char color, normal: color, f: text view format) // selected block, matching parenthesis, ... def left = clip position (ps, block start (ps)) def right = max (left, clip position (ps, block limit (ps))) def cc = highlight // colorize from start to left cc = colorize normal (ps, cc, normal, 0, left) // colorize from left to right as selected cc = colorize range (cc, right - left, selected back color (f)) // colorize from right to limit as normal cc = colorize normal (ps, cc, normal, right, display size (text line (ps)):int + 1) end //[c] //[c]Sub-functions: //[of]:colorize normal func colorize normal ( ps: paint line, cc: [] local char color, normal: color, start: int, limit: int) def ranges = ranges (ps) def col = start if not nil (ranges) def range index = range index (ps) while col < limit if range index >= size (ranges) break end def range = ranges [range index] : text view range def range left = clip position (ps, start (range), start, limit) def range right = clip position (ps, limit (range), start, limit) if range left > limit break end cc = colorize range (cc, range left - col, normal) if range right < limit col = range right range index += 1 else col = limit end //cc = colorize range (cc, col - range left, 0xFFC0B0) def colors = marker colors (ps) def index = type (marker (range)) cc = colorize range (cc, col - range left, colors [index]) end range index (ps) = range index end // colorize from right to limit as normal cc = colorize range (cc, limit - col, normal) return cc end //[cf] //[of]:colorize range //[c]Colorize a range //[c] func colorize range ( cc: [] local char color, size: int, color: color) def count = size if count>0 repeat cc[].back color = color cc += 1 if --count==0 break end end end return cc end //[cf] //[of]:clip position //[c]Returns the given location clipped to the line //[c] func clip position (ps: paint line, tp: text position) def cmp = tp.line - ps.line if cmp < 0 // above -> beginning of line return 0 elsif cmp > 0 // below -> end of line (after cariage return) return display size (text line (ps)):int + 1 else // same line -> use column return column (tp) end end //[cf] //[of]:clip position //[c]Returns the given location clipped to the line //[c] func clip position (ps: paint line, tp: text position, start: int, limit: int) def cmp = tp.line - ps.line if cmp < 0 // above -> beginning of line return start elsif cmp > 0 // below -> end of line (after cariage return) return display size (text line (ps)):int + 1 else def col = column (tp) if col < start return start elsif col > limit return limit else return col end end end //[cf] //[cf] //[of]:colorize mono //[c]Initialize a list of char colors from a line and and initial state //[c] public func colorize mono (str: string, cc: [] local char color, color: color) def p = str repeat if is nul (p[]) break end cc[].fore color = color ++p ++cc end end //[cf] //[cf] //[c] //[c]See also: //[l]:paint line structure:#text-view/private-painting/paint-line //[cf] //[of]:update pointer private func update pointer (m: text view) def pointer: cursor def pt := cursor position (m) def pos := text position under point (m, pt) def line = text line (pos) if is headline (line) pointer = hand pointer (m) else pointer = ibeam pointer (m) end set cursor (m, pointer) end //[cf] //[cf] //[of]:editing //[of]:insert or transform private func insert or transform (m: text view, dst type: byte, src type: byte) if is read only (m) return end if is selection empty (m) insert empty line (m, dst type) else def text = text (m) start group (text) def lines := list of lines (cursor (m), block (m)) // Convert all text lines each (lines) ? line if type (line) == src type type (line) = dst type end end // Special case: if the selection contains only one line, // its type is default: // - its type must be forced // - a default line must be added def first = first (lines) if first == last (lines) && not nil (first) type (first) = dst type add (lines, new empty default line) end delete selection (m) replace selection (m, first, default cursor behavior) delete lines (lines) stop group (text) end end //[cf] //[of]:insert empty line func insert empty line (m: text view, type: byte) // cursor to the beginning of line cursor to (m, line (cursor (m)), 0, false) def t1 = new text line (0, type) def t2 = new empty default line prev (t1) = nil next (t1) = t2 prev (t2) = t1 next (t2) = nil replace selection (m, t1, default cursor behavior) delete (t1) delete (t2) end //[cf] //[of]:insert tab func insert tab (m: text view, shift: bool) // if several lines are selected: indent or unindent def line 1 = text line (cursor (m)) def line 2 = text line (block (m)) if line 1 <> line 2 if shift unindent selection (m) else indent selection (m) end return end if expand tabulation (format (m)) def pos = min (cursor (m), block (m)) def n = spaces to next tab (m, text line (pos), column (pos)) def s := temp string buffer while n > 0 s << \w -- n end insert string (m, as string (s)) release (s) else insert char (m, \t) end end //[cf] //[of]:insert char func insert char (m: text view, ch: char) // handle overwrite mode def c = cursor (m) if is overwrite (m) && is selection empty (m) && not last column (c) cursor to (m, line (c), column (c) + 1, true) end def s : [1] char s [] = ch def tmp line = new text line (s, 1, lt default) prev (tmp line) = nil next (tmp line) = nil replace selection (m, tmp line, append cursor behavior) delete (tmp line) end //[cf] //[of]:insert string //[c]Insert a string (single line please) //[c] func insert string (m: text view, s: string) def tmp line = new text line (s, size (s), lt default) prev (tmp line) = nil next (tmp line) = nil replace selection (m, tmp line, append cursor behavior) delete (tmp line) end //[cf] //[of]:insert new line func insert new line (m: text view) // Auto indentation def i = 0 def line = text line (cursor (m)) def p = buf (line) if auto indentation (format (m)) def n = column (cursor (m)) while i < n def c = p [i] if ~ is blank (c) break end ++i end end def size = i def t1 = new empty default line def t2 = new text line (buf (line), size, lt default) prev (t1) = nil next (t1) = t2 prev (t2) = t1 next (t2) = nil replace selection (m, t1, append cursor behavior) delete (t1) delete (t2) end //[cf] //[c] //[of]:delete forward char //[c]Delete char after cursor (selection must be empty) //[c] func delete forward char (m: text view) def c = block (m) if not last column (c) column (c) += 1 elsif not last line (c) text line (c) = next (text line (c)) line (c) += 1 column (c) = 0 else return end delete selection (m) end //[cf] //[of]:delete backward char //[c]Delete char before cursor (selection must be empty) //[c] func delete backward char (m: text view) def c = block (m) if not first column (c) column (c) -= 1 elsif not first line (c) text line (c) = prev (text line (c)) line (c) -= 1 column (c) = size (text line (c)) : int else return end delete selection (m) end //[cf] //[of]:delete indentation //[c]Delete whitespaces from cursor to previous tab position //[c]- selection must be empty //[c]- there must be only white spaces before cursors //[c] func delete indentation (m: text view) def block = block (m) def col = column (block) def tab = tabulation size (format (m)) def n = col % tab if n == 0 && col > 0 n = tab end column (block) -= n delete selection (m) end //[cf] //[c] //[of]:replace selection (lines, behavior) func replace selection ( m: text view, lines: text line, behavior: selection behavior) def start = cursor (m) def limit = block (m) // Sort positions if limit < start def tmp = start start = limit limit = tmp end cursor behavior (m) = behavior replace text (text (m), start, limit, lines) cursor behavior (m) = default cursor behavior end //[cf] //[of]:replace selection (lines, point) func replace selection (m: text view, lines: text line, point: point) def pos := text position under point (m, point) cursor behavior (m) = select cursor behavior replace text (text (m), pos, pos, lines) cursor behavior (m) = default cursor behavior end //[cf] //[c] //[of]:paste text //[c]Paste selection from clipboard //[c] private func paste text (m: text view, f: clipboard format) def t := get clipboard data (f) def lines := list of lines each line (base (t): string) ? buf, len add (lines, buf, len) end replace selection (m, first (lines), append cursor behavior) delete lines (lines) release (t) return true end //[cf] //[of]:paste structured text //[c]Paste selection from clipboard //[c] private func paste structured text (m: text view, f: clipboard format) def t := get clipboard data (f) def reader := memory reader (base (t)) def lines := list of lines unserialize (lines, reader) replace selection (m, first (lines), append cursor behavior) delete lines (lines) release (t) return true end //[cf] //[of]:receive text //[c]Paste selection from primary selection //[c] private func receive text (m: text view, buffer: [] byte, size: size) def lines := list of lines each line (buffer: string, size) ? buf, len add (lines, buf, len) end replace selection (m, first (lines), query point (m)) delete lines (lines) end //[cf] //[of]:receive structured text //[c]Paste selection from primary selection //[c] private func receive structured text (m: text view, buffer: [] byte, size: size) def reader := memory reader (buffer) def lines := list of lines unserialize (lines, reader) replace selection (m, first (lines), query point (m)) delete lines (lines) end //[cf] //[cf] //[of]:moving cursor //[of]:move cursor with autoscroll private func move cursor with autoscroll (m: text view, p: point, shift: bool) move cursor under point (m, p, shift) x (autoscroll position (m)) = x (p) y (autoscroll position (m)) = y (p) end //[cf] //[of]:move cursor under point private func move cursor under point (m: text view, p: point, shift: bool) def pos := text position under point (m, p) cursor to (m, line number (pos), column (pos), shift) end //[cf] //[of]:show cursor //[c]Show cursor //[c] //[c]- reset timer //[c]- force visible //[c]- invlidate line with cursor //[c] func show cursor (m: text view) start (cursor timer (m)) blinking state (m) = true invalidate cursor line (m) end //[c] //[cf] //[cf] //[of]:observing text //[of]:Description //[c]The widget is watching for external changes on the current //[c]edited text object. //[cf] //[c] //[of]:handle replace event private func handle replace event ( m: text view, o: text object, e: replace text event) // ignore events from children if text (m) <> o return end // correct positions def scrolled = fix position (page (m), e) def relocated = fix position (cursor (m), e) if relocated cursor x (m) = x (m, text line (cursor (m)), column (cursor (m))) end relocated |= fix position (block (m), e) // determine if following lines need to be recolorized def changed = false def line = next (text line (new limit (e))) def line number = line (new limit (e)) + 1 if not nil (line) if ~ range validated (e) def old = state (line) validate to (m, line number + 1) def new = state (line) range validated (e) = true range changed (e) = old <> new end changed = range changed (e) end // update subline numbers, count delta def line 1 = line number (new start (e)) def delta : int if word wrap (format (m)) def l = line 1 def n = original lines (e) def old = 0 while n > 0 old += number of sublines (m, l) l += 1 n -= 1 end replace (sublines (m), line 1, original lines (e), new lines (e)) // create a default canvas def canvas := canvas (m) line = text line (new start (e)) l = line 1 n = new lines (e) def new = 0 while n > 0 def sl = compute number of sublines (m, line, canvas) sublines (m) [l] = sl new += sl line = next (line) l += 1 n -= 1 end release (canvas) delta = new - old sublines count (m) += delta // the page subline may be invalid if scrolled reset page subline (m) end else delta = delta line (e) end // move cursor and force it to be visible // It can be done only when everything is fixed (word wrap tables, ...) switch cursor behavior (m) case select cursor behavior cursor to (m, line number (new start (e)), column (new start (e)), false) cursor to (m, line number (new limit (e)), column (new limit (e)), true) case append cursor behavior cursor to (m, line number (new limit (e)), column (new limit (e)), false) else adjust page (m) end // invalidate def line 2 : int if (delta == 0) && ~ changed && ~ scrolled line 2 = line (new limit (e)) else line 2 = line number (page (m)) + page height inc (m) // bottom of page or more (if word wrap) end invalidate (m, line 1, line 2) update pointer (m) // send a notification on value changed (m) // send a selection changed if needed if relocated on selection changed (m) end // send a page changed if needed if scrolled on page changed (m) end end //[cf] //[of]:handle prop changed private func handle prop changed ( m: text view, o: text object, e: replace prop event) // ignore events from children if text (m) <> o return end // update subline numbers, count delta def line object = line object (e) def line 1 = line number (e) def line 2 = line 1 if word wrap (format (m)) def old = number of sublines (m, line 1) def canvas := canvas (m) def new = compute number of sublines (m, line object, canvas) release (canvas) def delta = new - old if delta <> 0 line 2 = line number (page (m)) + page height inc (m) // bottom of page or more (if word wrap) sublines (m) [line 1] = new sublines count (m) += delta end end // invalidate invalidate (m, line 1, line 2) update pointer (m) // move cursor to the modified line if cursor behavior (m) == append cursor behavior cursor to (m, line 1, 0, false) end // send a notification on value changed (m) end //[cf] //[of]:handle destroyed //[c]Handle destruction of the text object //[c] private func handle destroyed ( m: text view, o: text object) // ignore events from children if text (m) <> o return end // do not clear pointer since parent components // handle this event too and read this value // to determine wheter they are concerned or not //text (m) = nil end //[cf] //[of]:handle marker added private func handle marker added ( m: text view, o: text object, e: text marker) // ignore events from children if text (m) <> o return end // Do not update the ranges if they are already invalid: // the ranges will be rebuilt on next paint event. if valid ranges (m) add marker (m, e) end invalidate (m, line (start (e)), line (limit (e))) end //[cf] //[of]:handle marker removed private func handle marker removed ( m: text view, o: text object, e: text marker) // ignore events from children if text (m) <> o return end // Just mark the list of ranges as invalid. // the ranges will be rebuilt on next paint event. valid ranges (m) = false invalidate (m, line (start (e)), line (limit (e))) end //[cf] //[cf] //[of]:observing timers //[c]A timer is used to have a blinking cursor //[c] //[of]:handle cursor timer private func handle cursor timer (m: text view, t: timer) blinking state (m) = ~ blinking state (m) invalidate cursor line (m) end //[cf] //[of]:handle autoscroll timer private func handle autoscroll timer (m: text view, t: timer) // Do not autoscroll if inside client area def pos = autoscroll position (m) def client = client rect (m) def x = x (pos) def y = y (pos) if x >= 0 && x < width (client) && y >= 0 && y < height (client) return end move cursor under point (m, pos, true) end //[cf] //[cf] //[of]:base functions //[of]:invalidate (line1, line2) //[c]Invalidates a range of lines //[c] //[c]The range is [line1, line2] or [line2, line1]. //[c] func invalidate (m: text view, line1: int, line2: int) // Reorder def first: int def last: int if line1 < line2 first = line1 last = line2 else first = line2 last = line1 end // Intersection with visible lines def page = page (m) def first subline = - subline (page) def line number = line number (page) def text line = text line (page) // find first subline if first < line number if last < line number return end else def h = page height inc (m) while first subline < h && line number < first first subline += number of sublines (m, line number) line number += 1 text line = next (text line) end end // find last subline def last subline = first subline while last subline <= page height inc (m) && line number <= last if not nil (text line) last subline += number of sublines (m, line number) text line = next (text line) else last subline += 1 end line number += 1 end if first subline < 0 first subline = 0 end if last subline < 0 last subline = 0 elsif last subline > page height inc (m) last subline = page height inc (m) end // Invalidate rectangle def sublines = last subline - first subline def font height = font height (m) invalidate ( m, rectangle ( 0, first subline * font height, width (client rect (m)), sublines * font height)) end //[cf] //[of]:invalidate (line) //[c]Invalidates a single line //[c] func invalidate (m: text view, line: int) invalidate (m, line, line) end //[cf] //[of]:invalidate selection func invalidate selection (m: text view) def line 1 = line number (cursor (m)) def line 2 = line number (block (m)) invalidate (m, line 1 , line 2) end //[cf] //[of]:invalidate cursor line //[c]Invalidates the line containing the cursor //[c] private func invalidate cursor line (m: text view) invalidate (m, line number (cursor (m))) end //[cf] //[c] //[of]:on enter func on enter (m: text view) on enter (m, event (enter event type)) end //[cf] //[of]:on leave func on leave (m: text view) on leave (m, event (leave event type)) end //[cf] //[of]:on page changed func on page changed (m: text view) on page changed (m, event (page changed event type)) end //[cf] //[of]:on cursor mode func on cursor mode (m: text view) on cursor mode (class (m)) {m, event (cursor mode event type)} end //[cf] //[of]:on enter func on enter (m: text view, e: event) on enter (class (m)) {m, e} end //[cf] //[of]:on leave func on leave (m: text view, e: event) on leave (class (m)) {m, e} end //[cf] //[of]:on page changed func on page changed (m: text view, e: event) on page changed (class (m)) {m, e} end //[cf] //[c] //[of]:spaces to next tab //[c]Returns the number of spaces to expand to next tab //[c] func spaces to next tab (m: text view, text line: text line, column: int) def tab size = tabulation size (format (m)) def next tab = tab size def p = buf (text line) def x = 0 while column > 0 def c = p++[] if c==\0 break end -- column // Write the char in the buffer // - Tabulation are expanded here if c==\t while next tab <= x next tab += tab size end x = next tab else ++ x end end while next tab <= x next tab += tab size end return next tab - x end //[cf] //[of]:text point //[c]Converts column to x //[c] struct text point x : int subline : int end //[c] func text point (m: text view, text line: text line, column: int, return pt: text point) // create a default canvas def font = font (m, text line) def canvas := canvas (m) set font (canvas, font) // Advance to the right subline def subline = 0 def p = buf (text line) def pos = (p+column) def next subline = next offset (m, p, canvas) while pos : [] byte >= next subline : [] byte && not nul (next subline[]) p = next subline next subline = next offset (m, p, canvas) subline += 1 end // Correct column: column inside subline column = pos - p // Advance to column def x = 0 def tab size = tab size in pixels (m, canvas) def next tab = tab size def buf: [max range length] char def c: char while column > 0 //[of]: read blanks def q = buf while column > 0 c = p[] if is nul (c) break end if ~ is blank (c) break end ++p --column // Write the char in the buffer // - Tabulation are expanded here if c==\t def len = q-buf if len > 0 x += text width (canvas, buf, len) end while next tab <= x next tab += tab size end x = next tab q = buf else q++[] = c end end def len = q-buf if len > 0 x += text width (canvas, buf, len) end //[cf] //[of]: w = read word q = buf while column > 0 c = p[] if is nul (c) break end if is blank (c) break end ++p --column q++[] = c end len = q-buf if len > 0 x += text width (canvas, buf, len) end //[cf] if is nul (c) break end end release (canvas) x (pt) = x subline (pt) = subline end //[cf] //[of]:x //[c]Converts column to x //[c] func x (m: text view, text line: text line, column: int) return x (text point (m, text line, column)) end //[cf] //[of]:column //[c]Converts x to column //[c] func column (m: text view, text line: text line, subline: int, x: int) // create a default canvas def canvas := canvas (m) def font = font (m, text line) set font (canvas, font) def len = 0 def tab size = tab size in pixels (m, canvas) def next tab = tab size def p = buf (text line) while subline > 0 p = next offset (m, p, canvas) subline -= 1 end def limit = next offset (m, p, canvas) if not nul (limit []) // Not the last line: // 1. prevent to select the last character because it is on next subline // A reverse conversion would return subline + 1. // 2. the line cannot be empty, we can decrement safely the limit limit -= 1 end while p : [] byte < limit : [] byte def c = p [] if c == \0 break end def prev len = len if c == \t while next tab <= len next tab += tab size end len = next tab else def buf: [1] char buf[] = c len += text width (canvas, buf, 1) end if (prev len + len) / 2 > x break end p++ end release (canvas) return p - buf (text line) end //[cf] //[c] //[of]:font //[c]Returns the right font for a given line of text //[c] func font (m: text view, line: text line) def f = format (m) if is text (line) return text font (f) elsif is comment (line) return comment font (f) elsif is folder (line) return folder font (f) else return link font (f) end end //[cf] //[of]:tab size in pixels func tab size in pixels (m: text view, canvas: canvas) return tabulation size (format (m)) * ave char width (canvas) end //[cf] //[of]:text line //[c]Converts a line number to a text line //[c] //[c]This method is a bit optimized: it starts the search from the //[c]nearest position around the given line number. //[c] //[c]IMPORTANT: This method must not be invoked while modifiying //[c]the page or the cursor object since it relies on them to find //[c]the text line. //[c] func text line (m: text view, line: int) def count = line def dist = line def text line = first line (text (m)) def page = page (m) def new dist = abs (line number (page) - line) if new dist < dist dist = new dist count = line - line number (page) text line = text line (page) end def cursor = cursor (m) new dist = abs (line number (cursor) - line) if new dist < dist count = line - line number (cursor) text line = text line (cursor) end if count > 0 while count > 0 & not nil (text line) text line = next (text line) count -= 1 end elsif count < 0 while count < 0 & not nil (text line) text line = prev (text line) count += 1 end end return text line end //[cf] //[of]:subline func subline (m: text view, line: text line, column: int) if ~ word wrap (format (m)) return 0 end def sl = 0 def x = 0 def start = display text (line) def p = start repeat end return sl end //[cf] //[of]:number of sublines func number of sublines (m: text view, line number: int) // Optimisation: when word wrapping is off, there is always one // subline. if ~ word wrap (format (m)) return 1 else return sublines (m) [line number] end end //[cf] //[c] //[of]:reset sublines and page public func reset sublines and page (m: text view) // Reset table of sublines reset sublines (m) // Reset the subline of the page // The value may be invalid reset page subline (m) end //[cf] //[of]:reset sublines //[c]Resets the vector of sublines and updates cursor x //[c] private func reset sublines (m: text view) def sublines = sublines (m) remove all (sublines) // do not count sublines if no word wrap if word wrap (format (m)) def text = text (m) reserve (sublines, size (text)) // create a default canvas def canvas := canvas (m) // count sublines def sum = 0 each line (text) ? line def n = compute number of sublines (m, line, canvas) add (sublines, n) sum += n end sublines count (m) = sum // free canvas release (canvas) end // Reset the 'cursor x' value def cursor = cursor (m) cursor x (m) = x (m, text line (cursor), column (cursor)) end //[cf] //[of]:reset page subline //[c]This method is invoked when restoring a folder from stack and //[c]when the page location points to replaced text. //[c] //[c]The subline is the minimum of the current page subline and the //[c]last valid subline. //[c] public func reset page subline (m: text view) def page := page view position (m) def last subline = number of sublines (m, line number (page)) - 1 if subline (page) > last subline subline (page) = last subline adjust x (m, page) move page (m, page) end end //[cf] //[of]:compute number of sublines private func compute number of sublines ( m: text view, text line: text line, canvas: canvas) // create a default canvas set font (canvas, font (m, text line)) // Advance to the right subline def subline = 0 def p = display text (text line) repeat p = next offset (m, p, canvas) subline += 1 if is nul (p[]) break end end return subline end //[cf] //[c] //[of]:next offset //[c]Returns the pointer to the next subline. //[c] //[c]This is the main function for word wrapping. This method is the only //[c]one that knows how to cut a line into sublines. //[c] //[c]If there is no word wrapping or if it is the last subline, it returns a //[c]pointer to the nul terminal char. //[c] func next offset (m: text view, p: string, canvas: canvas) if ~ word wrap (format (m)) return p + size (p) end def x = 0 def tab size = tab size in pixels (m, canvas) def next tab = tab size def c: char def buf: [max range length] char repeat //[of]: read blanks def q = buf repeat c = p[] if is nul (c) break end if ~ is blank (c) break end ++p // Write the char in the buffer // - Tabulation are expanded here if c==\t def len = q-buf if len > 0 x += text width (canvas, buf, len) end while next tab <= x next tab += tab size end x = next tab q = buf else q++[] = c end end def len = q-buf if len > 0 x += text width (canvas, buf, len) end //[cf] //[of]: w = read word q = buf def word start = p repeat c = p[] if is nul (c) break end if is blank (c) break end ++p q++[] = c end def w = 0 len = q - buf if len > 0 w = text width (canvas, buf, len) end //[cf] // never break at the beginning of line if x == 0 x += w else x += w if x > width (client rect (m)) p = word start break end end if is nul (c) break end end return p end //[cf] //[cf] //[of]:accessing //[of]:set edit mode //[c]Sets mouse mode to 'edit mode' //[c] private func set edit mode (m: text view) if mouse mode (m) == tv select mode stop capture (m) stop (autoscroll timer (m)) end mouse mode (m) = tv edit mode end //[cf] //[of]:set select mode //[c]Sets mouse mode to 'edit mode' //[c] private func set select mode (m: text view) if mouse mode (m) <> tv select mode mouse mode (m) = tv select mode start capture (m) start (autoscroll timer (m)) end end //[cf] //[c] //[of]:margin //[c]Returns the margin used when scrolling page horizontally //[c] func margin (m: text view) def canvas := canvas (m) set font (canvas, text font (format (m))) equ half width = width (client rect (m)) / 2 equ eight char width = 8 * ave char width (canvas) release (canvas) return min (half width, eight char width) end //[cf] //[of]:page height inc //[c]Gets page height (number of lines) //[c] func page height inc (m: text view) return (height (client rect (m)) - 1) / font height (m) + 1 end //[cf] //[of]:reset font height //[c]Computes font height //[c] //[c] this method is invoked when the format changes //[c] func reset font height (m: text view) def canvas := canvas (m) def f = format (m) set font (canvas, text font (f)) def h = height (canvas) set font (canvas, comment font (f)) def h2 = height (canvas) if h2 > h h = h2 end set font (canvas, folder font (f)) h2 = height (canvas) if h2 > h h = h2 end set font (canvas, link font (f)) h2 = height (canvas) if h2 > h h = h2 end font height (m) = h release (canvas) end //[cf] //[of]:class equ class (m: text view) = class (super (m)) : text view class //[cf] //[cf] //[of]:testing //[of]:can unindent //[c]Returns true if the backspace key must delete backward indentation //[c] //[c]This is valid when the expand tab mode is on and there is only //[c]whitespaces before the cursor. //[c] private func can unindent (m: text view) def unindent = false def f = format (m) def c = cursor (m) if expand tabulation (f) && column (c) > 0 def text line = text line (c) def p = buf (text line) def limit = p + column (c) unindent = true while p : [] byte < limit : [] byte if p++[] <> \w unindent = false break end end end return unindent end //[cf] //[cf] //[of]:marker //[of]:add marker func add marker (m: text view, marker: text marker) // Lazy initialization of the list of ranges def ranges = ranges (m) if is nil (ranges) ranges = new vector (8) ranges (m) = ranges end // Search for the nearest previous range def nearest range = nil : text view range def i = size (ranges) - 1 while i <> unsigned (-1) def range = ranges [i] : text view range if start (range) < start (marker) nearest range = range break end i -= 1 end i += 1 // create the new range def new range = new text view range (marker) add (ranges, new range, i) i += 1 if not nil (nearest range) def limit = limit (nearest range) if limit <= start (marker) // no collision elsif limit <= limit (marker) // collsion - update limit set limit with start (nearest range, new range) else // the new marker is inside range def next range = new text view range (nearest range) set limit with start (nearest range, new range) set start with limit (next range, new range) add (ranges, next range, i) return end end // the new range is behind all successors, so we must cut the // new range with the next range and create background // segments between the next ranges if necessary // no more range ? def n = size (ranges) while i < n def range = ranges [i] : text view range i += 1 // finish before the beginning of the next range ? if limit (marker) <= start (range) return end // fixup the limit set limit with start (new range, range) // skip contigous ranges while i < n def next = ranges [i] : text view range if limit (range) < start (next) break end range = next i += 1 end // finish before the end of the next range ? if limit (marker) <= limit (range) return end // create the new range new range = new text view range (marker) set start with limit (new range, range) add (ranges, new range, i) i += 1 n += 1 end end //[cf] //[of]:validate ranges func validate ranges (m: text view) def text = text (m) if is nil (text) return end if valid ranges (m) return end valid ranges (m) = true def ranges = ranges (m) if not nil (ranges) each (ranges) ? range delete (range: text view range) end remove all (ranges) end each marker (text) ? marker add marker (m, marker) end end //[cf] //[cf] //[cf] //[of]:text view format //[of]:description //[c]Object to tell a text view how to display text. //[cf] //[l]:definition:#structures/text-view-format //[c] //[of]:initialize - release //[of]:release (m) //[c] public func release (m: text view format) delete (colorizer (m)) delete (text font (m)) delete (comment font (m)) delete (folder font (m)) delete (link font (m)) end //[cf] //[cf] //[c] //[cf] //[of]text-view-position:text view position //[of]:description //[c]A text view position locates a position on the displayed text. //[c] //[c]It differs from a text position: //[c]- a text position references a char in the text object //[c]- a text view position references a position in the text view, taking word //[c] wrapping into account. //[c]- it is design to perform arithmetics with lines and columns, that why //[c] the line object is not stored: it will cost too much for nothing to //[c] synchronize pointer especially when word wrapping is off. //[cf] //[l]:definition:#structures/text-view-position //[c] //[of]:initialize - release //[of]:initialize (m, view, line number, subline, x) //[c] public func initialize ( m: text view position, view: text view, line number: int, subline: int, x: int) view (m) = view line number (m) = line number subline (m) = subline x (m) = x end //[cf] //[cf] //[of]:operations //[of]:add (m, lines) //[c]Returns a view position 'n' sublines down. //[c] //[c]The result is bound by the last line. //[c] //[c]Note: the source position and returned position can be the same. //[c] public func add (m: text view position, n: int, return pos: text view position) : void if n < 0 sub (m, -n, pos) return end def view = view (m) def line = line number (m) def subline = subline (m) def size = size (text (view)) if ~ word wrap (format (view)) line += n if line >= size line = size - 1 end else repeat if line >= size line = size - 1 subline = number of sublines (view, line) - 1 break end def sl = number of sublines (view, line) if subline + n < sl subline += n break else n -= (sl - subline) line += 1 subline = 0 end end end view (pos) = view line number (pos) = line subline (pos) = subline x (pos) = x (m) end //[cf] //[of]:add forward (m, lines) //[c]Returns a view position 'n' sublines down. //[c] //[c]Same as 'add' but it advances to next line object if the //[c]resulting position is on a folder or link and subline > 0. //[c] public func add forward (m: text view position, n: int, pos: text view position) add (m, n, pos) // need to check if folder or link ? if n > 0 && subline (pos) > 0 def text = text (view (pos)) def line number = line number (pos) if line number < size (text) - 1 def line = text line (text, line number) if is headline (line) line number (pos) = line number + 1 subline (pos) = 0 end end end end //[cf] //[of]:sub (m, lines) //[c]Returns a view position 'n' sublines up. //[c] //[c]The number of lines must be positive or nul. The result is bound by the //[c]first lines. //[c] //[c]Note: the source position and returned position can be the same. //[c] //[c] public func sub (m: text view position, n: int, return pos: text view position) : void if n < 0 add (m, -n, pos) return end def view = view (m) def line = line number (m) def subline = subline (m) def size = size (text (view)) if ~ word wrap (format (view)) if line >= n line -= n else line = 0 end else repeat if n <= subline subline -= n break elsif line == 0 subline = 0 break else line -= 1 n -= subline + 1 subline = number of sublines (view, line) - 1 end end end view (pos) = view line number (pos) = line subline (pos) = subline x (pos) = x (m) end //[cf] //[cf] //[of]:accessing //[of]:set line (m, position) //[c]Copies the line number and subline from another position. //[c] public func set line (m: text view position, pos: text view position) line number (m) = line number (pos) subline (m) = subline (pos) end //[cf] //[cf] //[of]:testing //[of]:is first line (m) //[c]Returns true if the position refers to the first line //[c] public equ is first line (m: text view position) = line number (m) == 0 && subline (m) == 0 //[cf] //[of]:is last line (m) //[c]Returns true if the position refers to the last line //[c] public func is last line (m: text view position) def view = view (m) def line = line number (m) def subline = subline (m) return ( line == size (text (view)) - 1 && subline == number of sublines (view, line) - 1) end //[cf] //[of]:not first line (m) //[c]Returns true if the position does NOT refer to the first line //[c] public equ not first line (m: text view position) = ~ is first line (m) //[cf] //[of]:not last line (m) //[c]Returns true if the position does NOT refer to the last line //[c] public equ not last line (m: text view position) = ~ is last line (m) //[cf] //[c] //[of]:is above (p1, p2) //[c] public func is above (p1: text view position, p2: text view position) if line number (p1) < line number (p2) return true elsif line number (p1) > line number (p2) return false else return subline (p1) < subline (p2) end end //[cf] //[of]:is below (p1, p2) //[c] public func is below (p1: text view position, p2: text view position) if line number (p1) < line number (p2) return false elsif line number (p1) > line number (p2) return true else return subline (p1) > subline (p2) end end //[cf] //[cf] //[cf] //[of]page-position:page position //[of]:description //[c]A page position locates the upper left corner of the page //[cf] //[l]:definition:#structures/page-position //[c] //[of]:initialize - release //[of]:top left page position (text object) -> (m) //[c] public func top left page position (text: text object, return m: page position) x (m) = 0 line number (m) = 0 text line (m) = first line (text) subline (m) = 0 end //[cf] //[c] //[of]:page position (x, line number, text line) //[c] public func page position ( x: int, line number: int, text line: text line, m: page position) x (m) = x line number (m) = line number text line (m) = text line subline (m) = 0 end //[cf] //[of]:page position (line number, text line) //[c] public func page position ( line number: int, text line: text line, m: page position) x (m) = 0 line number (m) = line number text line (m) = text line subline (m) = 0 end //[cf] //[of]:copy (src) //[c] public func set (m: page position, o: page position) x (m) = x (o) line number (m) = line number (o) text line (m) = text line (o) subline (m) = subline (o) end //[cf] //[c] //[of]:initialize (m, x, line number, text line) //[c] public func initialize ( m: page position, x: int, line number: int, text line: text line) x (m) = x line number (m) = line number text line (m) = text line subline (m) = 0 end //[cf] //[cf] //[of]:updating //[of]:fix position (m, replace event) //[c]Updates a page position object according to the replace event parameters. //[c] //[c]Returns true if the position has moved. //[c] //[c]Note: the text line pointer may have changed even if the method //[c]returns false. //[c] //[c]Note 2: the subline is not updated. It is the responsibility of the owner to //[c]fix it. The best method is to compute the minimum of the current subline //[c]and the max subline allowed. //[c] public func fix position (m: page position, e: replace text event) def line number = line number (m) if line number < line number (original start (e)) return false end if line number > line number (original limit (e)) line number (m) += delta line (e) return false end text line (m) = text line (new start (e)) line number (m) = line number (new start (e)) return true end //[cf] //[cf] //[of]:accessing //[c]See structure attributes... //[cf] //[of]:testing //[of]:is first line (m) //[c]Returns true if the position refers to the first line //[c] public equ is first line (m: page position) = is nil (prev (text line (m))) //[cf] //[of]:is last line (m) //[c]Returns true if the position refers to the last line //[c] public equ is last line (m: page position) = is nil (next (text line (m))) //[cf] //[of]:not first line (m) //[c]Returns true if the position does NOT refer to the first line //[c] public equ not first line (m: page position) = not nil (prev (text line (m))) //[cf] //[of]:not last line (m) //[c]Returns true if the position does NOT refer to the last line //[c] public equ not last line (m: page position) = not nil (next (text line (m))) //[cf] //[cf] //[cf] //[of]:text view range //[of]:definition struct text view range marker: text marker start marker: text marker start uses limit: bool limit marker: text marker limit uses limit: bool end //[cf] //[of]:instance creation //[of]:new text view range (marker) func new text view range (marker: text marker) def m = allocate memory (sizeof local text view range): text view range marker(m) = marker start marker (m) = marker start uses limit (m) = false limit marker (m) = marker limit uses limit (m) = true return m end //[cf] //[of]:new text view range range) func new text view range (range: text view range) def m = allocate memory (sizeof local text view range): text view range marker(m) = marker (range) start marker (m) = start marker (range) start uses limit (m) = start uses limit (range) limit marker (m) = limit marker (range) limit uses limit (m) = limit uses limit (range) return m end //[cf] //[of]:delete func delete (m: text view range) free memory (m) end //[cf] //[cf] //[of]:accessing //[of]:start func start (m: text view range) def marker = start marker (m) return start uses limit (m) -> limit (marker), start (marker) end //[cf] //[of]:limit func limit (m: text view range) def marker = limit marker (m) return limit uses limit (m) -> limit (marker), start (marker) end //[cf] //[of]:set limit with start func set limit with start (m: text view range, new range: text view range) limit marker (m) = marker (new range) limit uses limit (m) = false end //[cf] //[of]:set start with limit func set start with limit (m: text view range, new range: text view range) start marker (m) = marker (new range) start uses limit (m) = true end //[cf] //[cf] //[cf]