pcs/edwin/struct.scm

437 lines
14 KiB
Scheme
Raw Permalink Normal View History

2023-05-20 05:57:04 -04:00
;;;
;;; Copyright (c) 1985 Massachusetts Institute of Technology
;;;
;;; This material was developed by the Scheme project at the
;;; Massachusetts Institute of Technology, Department of
;;; Electrical Engineering and Computer Science. Permission to
;;; copy this software, to redistribute it, and to use it for any
;;; purpose is granted, subject to the following restrictions and
;;; understandings.
;;;
;;; 1. Any copy made of this software must include this copyright
;;; notice in full.
;;;
;;; 2. Users of this software agree to make their best efforts (a)
;;; to return to the MIT Scheme project any improvements or
;;; extensions that they make, so that these may be included in
;;; future releases; and (b) to inform MIT of noteworthy uses of
;;; this software.
;;;
;;; 3. All materials developed as a consequence of the use of
;;; this software shall duly acknowledge such use, in accordance
;;; with the usual standards of acknowledging credit in academic
;;; research.
;;;
;;; 4. MIT has made no warrantee or representation that the
;;; operation of this software will be error-free, and MIT is
;;; under no obligation to provide any services, by way of
;;; maintenance, update, or otherwise.
;;;
;;; 5. In conjunction with products arising from the use of this
;;; material, there shall be no use of the name of the
;;; Massachusetts Institute of Technology nor of any adaptation
;;; thereof in any advertising, promotional, or sales literature
;;; without prior written consent from MIT in each case.
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Modified by Texas Instruments Inc 8/15/85
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(begin
(define-integrable %make-region
(lambda (start end)
(cons start end)))
(define-integrable region-start
(lambda (region)
(car region)))
(define-integrable region-end
(lambda (region)
(cdr region)))
(define-integrable region-group
(lambda (region)
(mark-group (region-start region))))
(define-integrable components->region
(lambda (start-line start-pos end-line end-pos)
(%make-region (mark-permanent! (%make-mark start-line start-pos #!FALSE))
(mark-permanent! (%make-mark end-line end-pos #!TRUE)))))
(define-integrable make-mark
(lambda (line position)
(%make-mark line position #!TRUE)))
(define-integrable %make-mark
(lambda (line position left-inserting?)
(let ((mark (make-vector 3)))
(vector-set! mark 0 line)
(vector-set! mark 1 position)
(vector-set! mark 2 left-inserting?)
mark)))
(define-integrable mark-line
(lambda (mark)
(vector-ref mark 0)))
(define-integrable %set-mark-line!
(lambda (mark line)
(vector-set! mark 0 line)))
(define-integrable mark-position
(lambda (mark)
(vector-ref mark 1)))
(define-integrable set-mark-position!
(lambda (mark position)
(vector-set! mark 1 position)))
(define-integrable mark-left-inserting?
(lambda (mark)
(vector-ref mark 2)))
(define-integrable mark-group
(lambda (mark)
(line-group (mark-line mark))))
(define-integrable line-tag 'line)
(define-integrable make-line
(lambda (string)
(let ((line (make-vector 8)))
(vector-set! line 3 line-tag)
(vector-set! line 1 string)
line)))
(define-integrable line-string
(lambda (line)
(vector-ref line 1)))
(define-integrable line-previous
(lambda (line)
(vector-ref line 2)))
(define-integrable line-next
(lambda (line)
(vector-ref line 0)))
(define-integrable line-marks
(lambda (line)
(vector-ref line 4)))
(define-integrable set-line-marks!
(lambda (line marks)
(vector-set! line 4 marks)))
(define-integrable line-group
(lambda (line)
(vector-ref line 5)))
(define-integrable set-line-group!
(lambda (line group)
(vector-set! line 5 group)))
(define-integrable line-number
(lambda (line)
(vector-ref line 6)))
(define-integrable set-line-number!
(lambda (line number)
(vector-set! line 6 number)))
(define-integrable line-alist
(lambda (line)
(vector-ref line 7)))
(define-integrable set-line-alist!
(lambda (line alist)
(vector-set! line 7 alist)))
)
;;;; Text Data Structures
;;; This file describes the data structures used to represent and
;;; manipulate text within the editor.
;;; The basic unit of text is the GROUP, which is essentially a type
;;; of character string with some special operations. Normally a
;;; group is modified by side effect; unlike character strings, groups
;;; will grow and shrink appropriately under such operations. Also,
;;; it is possible to have pointers into a group, called MARKs, which
;;; continue to point to the "same place" under these operations; this
;;; would not be true of a string, elements of which are pointed at by
;;; indices.
;;; As is stressed in the EMACS manual, marks point between characters
;;; rather than directly at them. This perhaps counter-intuitive
;;; concept may aid understanding.
;;; Besides acting as pointers into a group, marks may be compared.
;;; All of the marks within a group are totally ordered, and the
;;; standard order predicates are supplied for them. In addition,
;;; marks in different groups are unordered with respect to one
;;; another. The standard predicates have been extended to be false
;;; in this case, and another predicate, which indicates whether they
;;; are related, is supplied.
;;; Marks may be paired into units called REGIONs. Each region has a
;;; START mark and an END mark, and it must be the case that START is
;;; less than or equal to END in the mark ordering. While in one
;;; sense this pairing of marks is trivial, it can also be used to
;;; reduce overhead in the implementation since a region guarantees
;;; that its marks satisfy this very basic relation.
;;; As in most other editors of this type, there is a distinction
;;; between "temporary" and "permanent" marks. The purpose for this
;;; distinction is that temporary marks require less overhead to
;;; create. Conversely, temporary marks do not remain valid when
;;; their group is modified. They are intended for local use when it
;;; is known that the group will remain unchanged.
;;; The implementation of marks is different from previous
;;; implementations. In particular, it is not possible to tell
;;; whether a mark is temporary or permanent. Instead, a "caller
;;; saves"-like convention is used. Whenever any given mark needs to
;;; be permanent, one merely calls a procedure which "permanentizes"
;;; it. All marks are created temporary by default.
;;; Internally, groups are represented as an ordered set of objects,
;;; called LINEs, which are doubly linked to form a linear chain.
;;; Each line represents a string of characters without newlines, and
;;; two adjacent lines are separated by a "virtual newline". Thus
;;; this data structure directly corresponds to our intuitive concept
;;; of "line".
;;; In some sense the choice of lines are the unit of text is quite
;;; arbitrary; there are no real technical benefits to be gained from
;;; the choice. The decision to structure things this way was based
;;; on the fact that most current editors are built that way, and
;;; expediency demands that we not innovate too much.
;;; With that said, it is important to restate that lines are an
;;; INTERNAL data representation. Since the choice is arbitrary, they
;;; are not supported by any public operations.
;;;; Groups
;;; Every line belongs to a unique group, and every line belonging to
;;; the same group is related. That is, the lines in a group are
;;; totally ordered. Lines in different groups have no relation.
;;; There is no sharing of lines between groups. When lines are
;;; copied out of a group, they form a new group. When they are
;;; inserted into a group, they become part of that group.
(define make-group)
(let ()
(define group-tag 'group)
(set! make-group
(named-lambda (make-group region)
(let ((group (make-vector 6)))
(vector-set! group 2 group-tag)
(vector-set! group 1 region)
(vector-set! group 0 region)
(vector-set! group 5 #!FALSE)
group)))
)
(begin
(define-integrable group-index:total-region 1)
(define-integrable group-index:region 0)
(define-integrable group-index:delete-daemons 3)
(define-integrable group-index:insert-daemons 4)
(define-integrable group-index:read-only-flag 5)
(define-integrable group-region
(lambda (group)
(vector-ref group group-index:region)))
(define (%set-group-region! group region)
(vector-set! group group-index:total-region region)
(vector-set! group group-index:region region))
(define-integrable %group-start
(lambda (group)
(region-start (group-region group))))
(define-integrable %group-end
(lambda (group)
(region-end (group-region group))))
)
(define (group-read-only? group)
(vector-ref group group-index:read-only-flag))
(define (set-group-read-only! group)
(vector-set! group group-index:read-only-flag #!TRUE))
(define (set-group-writeable! group)
(vector-set! group group-index:read-only-flag #!FALSE))
;;;; Group Modification Daemons
(define (group-delete-daemons group)
(vector-ref group group-index:delete-daemons))
(define (add-group-delete-daemon! group daemon)
(vector-set! group group-index:delete-daemons
(cons daemon (vector-ref group group-index:delete-daemons))))
(define (region-delete-starting! region)
(if (group-read-only? (region-group region))
(editor-error "Trying to modify read only text."))
(region-modification-starting! (group-delete-daemons (region-group region))
region))
(define (group-insert-daemons group)
(vector-ref group group-index:insert-daemons))
(define (add-group-insert-daemon! group daemon)
(vector-set! group group-index:insert-daemons
(cons daemon (vector-ref group group-index:insert-daemons))))
(define (region-insert-starting! mark)
(if (group-read-only? (mark-group mark))
(editor-error "Trying to modified read only text."))
(region-modification-starting! (group-insert-daemons (mark-group mark))
mark))
(define (region-modification-starting! all-daemons argument)
(define (loop daemons)
(if (null? daemons)
'()
(let ((sync ((car daemons) argument)))
(if sync
(cons sync (loop (cdr daemons)))
(loop (cdr daemons))))))
(sync-daemons (loop all-daemons)))
(define ((sync-daemons daemons) region)
(define (loop daemons)
(if (not (null? daemons))
(begin ((car daemons) region)
(loop (cdr daemons)))))
(loop daemons))
;;;; Regions
(define (make-region start end)
(cond ((mark<= start end) (%make-region start end))
((mark<= end start) (%make-region end start))
(else (error "Marks not related" start end))))
(define (lines->region start-line end-line)
(let ((region (components->region start-line 0
end-line (line-length end-line))))
(set-line-group! start-line (make-group region))
(number-lines! start-line end-line)
region))
(define (region-components region receiver)
(receiver (mark-line (region-start region))
(mark-position (region-start region))
(mark-line (region-end region))
(mark-position (region-end region))))
;;;; Marks
(define (mark-components mark receiver)
(receiver (mark-line mark)
(mark-position mark)))
(define (mark-right-inserting mark)
(mark-permanent!
(if (mark-left-inserting? mark)
(%make-mark (mark-line mark) (mark-position mark) #!FALSE)
mark)))
(define (mark-left-inserting mark)
(mark-permanent!
(if (mark-left-inserting? mark)
mark
(%make-mark (mark-line mark) (mark-position mark) #!TRUE))))
;;;; Lines
;;; Instead of using VECTOR, MAKE-LINE is coded in a strange way to
;;; make it maximally fast. Both LIST->VECTOR and CONS are
;;; primitives. Also, VECTOR would cons a list, then vectorize it,
;;; creating a bunch of garbage, while this only makes one cons.
(define (set-line-string! line string)
(vector-set! line 1 string)
(set-line-alist! line '()))
(define (connect-lines! previous next)
(if (not (null? previous)) (vector-set! previous 0 next))
(if (not (null? next)) (vector-set! next 2 previous)))
(define (disconnect-lines! start end)
(vector-set! start 2 '())
(vector-set! end 0 '()))
;;; line-length clashes with a scheme-primitive. we have defined
;;; a macro line-length which will replace all occurrences of line-length
;;; to line-string-length. Maybe, we will change it all ove the source
;;; someday. The macro will be present only while compiling Edwin
;;; sources.
;;; (define-integrable (line-length line)
;;; (string-length (line-string line)))
;;;; Line Numbering
(define line-number-increment 256)
(define (number-lines! start-line end-line)
(define (number-upward group base increment)
(define (loop line number)
(set-line-group! line group)
(set-line-number! line number)
(if (not (eq? line end-line))
(loop (line-next line) (+ number increment))))
(loop start-line (+ base increment)))
(define (number-downward group base increment)
(define (loop line number)
(set-line-group! line group)
(set-line-number! line number)
(if (not (eq? line start-line))
(loop (line-previous line) (- number increment))))
(loop end-line (- base increment)))
(define (count-lines)
(define (loop line n)
(if (eq? line end-line)
n
(loop (line-next line) (1+ n))))
(loop start-line 1))
(let ((lower-limit (line-previous start-line))
(upper-limit (line-next end-line)))
(if (null? lower-limit)
(if (null? upper-limit)
;; Numbering entire group. The first line
;; had better be initialized correctly.
(number-upward (line-group start-line)
0
line-number-increment)
(number-downward (line-group upper-limit)
(line-number upper-limit)
line-number-increment))
(if (null? upper-limit)
(number-upward (line-group lower-limit)
(line-number lower-limit)
line-number-increment)
(number-upward (line-group lower-limit)
(line-number lower-limit)
(/ (- (line-number upper-limit)
(line-number lower-limit))
(1+ (count-lines))))))))