LineNumberImpl.mesa
Copyright Ó 1987, 1991, 1992 by Xerox Corporation. All rights reserved.
Peter B. Kessler, April 4, 1989 1:41:32 pm PDT
Michael Plass, March 10, 1992 0:39 am PST
Spreitze, September 11, 1992 9:31 am PDT
Willie-s, October 31, 1991 4:59 pm PST
Line number button for Tioga viewers.
DIRECTORY
Ascii USING [CR, LF],
Commander USING [CommandProc, Register],
Convert USING [IntFromRope, Error],
IO USING [PutFLR],
LineNumber,
Menus USING [AppendMenuEntry, CreateEntry, FindEntry, ReplaceMenuEntry, Menu,
MenuEntry, MenuFromMenuEntry, MenuLine, MenuProc],
MessageWindow USING [Append, Blink],
Rope USING [Find, Length, ROPE, Substr],
RopeReader USING [Ref, Create, SetPosition, Get, GetIndex],
TEditDocument USING [Selection, SelectionId],
TEditDocumentPrivate USING [findMenu],
TEditInput USING [CloseEvent, currentEvent, InterpretAtom],
TEditInputExtras USING [CommandClosureProc, RegisterClosure],
TEditInputOps USING [CallWithLocks, EditFailed],
TEditLocks USING [Access],
TEditSelection USING [Deselect, MakeSelection],
TEditSelectionOps USING [ShowGivenPositionRange],
TextEditBogus USING [GetRope],
TextEdit USING [Size, ReplaceByRope],
TextNode USING [Forward, Location, LocNumber, LocOffset, NodeItself, Ref, Root],
TiogaOps USING [GetSelection, Location, Ref, ViewerDoc],
ViewerClasses USING [Viewer],
ViewerOps USING [EnumProc, EnumerateViewers, PaintViewer, PaintHint],
ViewerTools USING [GetSelectionContents];
LineNumberImpl: CEDAR PROGRAM
IMPORTS
Commander, Convert, IO, Menus, MessageWindow, Rope, RopeReader, TEditDocumentPrivate, TEditInput, TEditInputExtras, TEditInputOps, TEditSelection, TEditSelectionOps, TextEdit, TextEditBogus, TextNode, TiogaOps, ViewerOps, ViewerTools
EXPORTS
LineNumber
~ {
Global state (bleah!).
previousLine: PRIVATE Rope.ROPE ¬ NIL;
Doesn't need a monitor, since you can live with whatever you get, given atomic writes.
Programmer's interface.
convertSelectionId: ARRAY LineNumber.SelectionId OF TEditDocument.SelectionId
~ [primary: primary, secondary: secondary, feedback: feedback];
ToSelectionRange: PUBLIC PROCEDURE [
viewer: ViewerClasses.Viewer,
lines: LineNumber.LaORange,
skipCommentNodes: BOOLEAN ¬ FALSE,
which: LineNumber.SelectionId ¬ feedback]
RETURNS [] ~ {
Given an viewer and a line number range, use the given selection to indicate those lines.
rootTextNodeRef: TiogaOps.Ref ~ TiogaOps.ViewerDoc[viewer: viewer];
positionRange: LineNumber.PositionRange ¬ LinesToPositions[
branch: rootTextNodeRef, lines: lines, skipCommentNodes: skipCommentNodes];
<<locI, locF: TextNode.Location;>>
IF positionRange.finalPosition=positionRange.initialPosition
THEN positionRange.finalPosition ¬ positionRange.initialPosition+3;
<<locI ¬ TextNode.LocWithin[rootTextNodeRef, positionRange.initialPosition, 1, skipCommentNodes];
locF ¬ TextNode.LocWithin[rootTextNodeRef, positionRange.finalPosition, 1, skipCommentNodes];
IF locI.node=locF.node AND locI.node#NIL AND locI.node.rope.Length[]=0 THEN {
Kludgy attempt to get around the invisibility of nodes. Sigh.
Back up to last character(s) of previous node (or line), and next character
fuzz: LineNumber.Position ~ 2;
positionRange.initialPosition ¬ IF positionRange.initialPosition < fuzz
stay in non-negatives!
THEN 0
ELSE positionRange.initialPosition - fuzz;
positionRange.finalPosition ¬
IF positionRange.finalPosition >= LineNumber.lastPosition - fuzz
stay in non-negatives!
THEN LineNumber.lastPosition
ELSE positionRange.finalPosition + fuzz;
};>>
TEditSelectionOps.ShowGivenPositionRange[
viewer: viewer,
selectionId: convertSelectionId[which],
posI: positionRange.initialPosition,
posF: positionRange.finalPosition,
skipCommentNodes: skipCommentNodes,
pendingDelete: FALSE];
};
ToFeedbackSelection: PUBLIC PROCEDURE [
viewer: ViewerClasses.Viewer,
line: LineNumber.Line,
offsetRange: LineNumber.OffsetRange ¬ [],
skipCommentNodes: BOOLEAN ¬ FALSE]
RETURNS [] ~ {
Given an viewer, a (non-negative) line number, and an offset range, indicate the identified portion of that line with the feedback selection.
ToSelectionRange[viewer, [[line, offsetRange.first], [line, offsetRange.last]], skipCommentNodes];
RETURN};
LinesToPositions: PUBLIC PROC [branch: TextNode.Ref, lines: LineNumber.LaORange, skipCommentNodes: BOOL ¬ FALSE] RETURNS [ans: LineNumber.PositionRange ¬ [0, 0]] ~ {
Given a node and a line number, returns the position range in the document of the line number.
oneLine: BOOL ~ lines.first.line=lines.last.line;
needFirst: BOOL ¬ TRUE;
PerLine: PROC [line: LineNumber.Line, positionRange: LineNumber.PositionRange] RETURNS [quit: BOOL] --LineNumber.EnumProc-- ~ {
IF needFirst AND line>=lines.first.line THEN {
needFirst ¬ FALSE;
ans.initialPosition ¬ positionRange.initialPosition + (
IF line=lines.first.line AND lines.first.offset#LineNumber.noOffset
THEN lines.first.offset ELSE 0)};
ans.finalPosition ¬ (
IF line=lines.last.line AND lines.last.offset#LineNumber.noOffset
THEN positionRange.initialPosition+lines.last.offset
ELSE positionRange.finalPosition);
RETURN [line >= lines.last.line]};
IF lines.last.line < lines.first.line THEN lines ¬ [lines.last, lines.first];
IF oneLine AND lines.last.offset < lines.first.offset
THEN lines ¬ [lines.last, lines.first];
[] ¬ Enumerate[branch, PerLine, skipCommentNodes];
IF needFirst OR (oneLine AND lines.first.offset=LineNumber.noOffset AND lines.last.offset#LineNumber.noOffset) THEN ans.initialPosition ¬ ans.finalPosition;
RETURN};
ToPositionRange: PUBLIC PROCEDURE [
branch: TextNode.Ref,
line: LineNumber.Line,
offsetRange: LineNumber.OffsetRange ¬ [],
skipCommentNodes: BOOLEAN ¬ FALSE]
RETURNS [LineNumber.PositionRange] ~ {
RETURN LinesToPositions[branch, [[line, offsetRange.first], [line, offsetRange.last]], skipCommentNodes]};
PositionsToLines: PUBLIC PROCEDURE [
branch: TextNode.Ref,
positions: LineNumber.PositionRange,
skipCommentNodes: BOOLEAN ¬ FALSE]
RETURNS [ans: LineNumber.LaORange ¬ [[0], [0]]] ~ {
needFirst: BOOL ¬ TRUE;
PerPos: PROC [line: LineNumber.Line, positionRange: LineNumber.PositionRange] RETURNS [quit: BOOL] --LineNumber.EnumProc-- ~ {
IF needFirst AND positionRange.finalPosition >= positions.initialPosition THEN {
needFirst ¬ FALSE;
ans.first ¬ [line, MAX[0, positions.initialPosition-positionRange.initialPosition]]};
ans.last ¬ [line, MIN[positionRange.finalPosition, positions.finalPosition] - positionRange.initialPosition];
RETURN [positionRange.finalPosition >= positions.finalPosition]};
IF positions.finalPosition < positions.initialPosition
THEN positions ¬ [positions.finalPosition, positions.initialPosition];
[] ¬ Enumerate[branch: branch, enumProc: PerPos, skipCommentNodes: skipCommentNodes];
IF needFirst THEN ans.first ¬ ans.last;
RETURN};
ToLine: PUBLIC PROCEDURE [
branch: TextNode.Ref,
position: LineNumber.Position,
skipCommentNodes: BOOLEAN ¬ FALSE]
RETURNS [LineNumber.LaO] ~ {
Given a node and a position, returns the line number in the document of the position.
RETURN [PositionsToLines[branch, [position, position], skipCommentNodes].first]};
Enumerate: PUBLIC PROCEDURE [
branch: TextNode.Ref,
enumProc: LineNumber.EnumProc,
skipCommentNodes: BOOLEAN ¬ FALSE]
RETURNS [quit: BOOLEAN ¬ FALSE] ~ {
Calls the enumProc with the line number and position range for each line from branch,
returns TRUE when any call of enumProc returns TRUE.
positionRange: LineNumber.PositionRange ¬ [0, 0];
line: LineNumber.Line ¬ 0;
firstNode: TextNode.Ref ¬ branch;
first: BOOL ¬ TRUE;
EachNode: NodeActionProc ~ {
PROCEDURE [node: node] RETURNS [quit: BOOLEANFALSE]
contents: Rope.ROPE ~ TextEditBogus.GetRope[node];
contentsLength: INT ~ Rope.Length[contents];
reader: RopeReader.Ref ~ RopeReader.Create[];
leftStart: INT ¬ 0;
lineLength: INT ¬ 0;
EachLine: PROCEDURE [] RETURNS [quit: BOOLEAN ¬ FALSE] ~ {
line ¬ line + 1;
positionRange.finalPosition ¬ positionRange.initialPosition + lineLength - 1;
quit ¬ enumProc[line: line, positionRange: positionRange];
positionRange.initialPosition ¬ positionRange.initialPosition + lineLength;
RETURN};
IF first THEN {
positionRange.initialPosition ¬ TextNode.LocOffset[[branch, 0], [node, 0], 1, skipCommentNodes];
first ¬ FALSE};
reader.SetPosition[rope: contents];
WHILE NOT quit AND leftStart <= contentsLength DO
ScanForCR: PROCEDURE [] RETURNS [foundCR: BOOLEAN ¬ FALSE] ~ {
WHILE reader.GetIndex[] < contentsLength DO {
IF NewLine[reader.Get[]] THEN {
foundCR ¬ TRUE;
EXIT;
};
} ENDLOOP;
RETURN [foundCR: foundCR];
};
IF ScanForCR[]
THEN {
lineLength ¬ reader.GetIndex[] - leftStart;
}
ELSE {
lineLength ¬ contentsLength - leftStart + 1;
include end-of-node in line.
};
quit ¬ EachLine[];
leftStart ¬ leftStart + lineLength;
ENDLOOP;
RETURN};
quit ¬ TreeWalkMap[
branch: branch,
skipCommentNodes: skipCommentNodes,
action: EachNode];
RETURN [quit: quit];
};
Menu procs.
AppendMenuButton:
PUBLIC PROCEDURE [menu: Menus.Menu, line: Menus.MenuLine ¬ 0] ~ {
name: Rope.ROPE ~ "Line";
entry: Menus.MenuEntry ~ Menus.CreateEntry[
name: name,
proc: MenuProc,
clientData: NIL,
documentation: "highlight selected line number.",
fork: TRUE,
guarded: FALSE];
old: Menus.MenuEntry ~ Menus.FindEntry[menu: menu, entryName: name];
IF old = NIL
THEN {
Menus.AppendMenuEntry[menu: menu, entry: entry, line: line];
}
ELSE {
Menus.ReplaceMenuEntry[menu: menu, oldEntry: old, newEntry: entry];
};
};
MenuProc: PUBLIC Menus.MenuProc ~ {
PROC [
parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
TEditInput.InterpretAtom[
viewer: parent,
atom: SELECT mouseButton FROM
red => IF shift THEN $ShowLine ELSE $ShowLineWithoutComments,
yellow => IF shift THEN $MessageLine ELSE $MessageLineWithoutComments,
blue => $StuffLine,
ENDCASE => ERROR
];
};
TiogaOps.
ShowLineOp: PRIVATE TEditInputExtras.CommandClosureProc ~ {
PROC [data: REF ANY, viewer: ViewerClasses.Viewer ← NIL, param: REFNIL]
RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE]
Given a line number as the param or in the selection, indicate that line in the viewer.
This is a TEditInputExtras.CommandClosureProc so it can pick up the param.
skipCommentNodes: BOOLEAN ~ NARROW[data, REF BOOLEAN]­;
first, last: LineNumber.LaO ¬ [0];
ParseLaO: PROC [rope: Rope.ROPE] RETURNS [LineNumber.LaO] ~ {
cmLoc: INT ~ rope.Find[","];
IF cmLoc=-1 THEN RETURN [[NatFromRope[rope]]];
RETURN [[
line: NatFromRope[rope.Substr[len: cmLoc]],
offset: NatFromRope[rope.Substr[start: cmLoc+1]] ]]};
WITH param SELECT FROM
int: REF INT => first ¬ last ¬ [int­];
ENDCASE => {ENABLE Convert.Error => GO TO bad;
selectionRope: Rope.ROPE ~ ViewerTools.GetSelectionContents[];
ddLoc: INT ~ selectionRope.Find[".."];
IF ddLoc=-1 THEN first ¬ last ¬ ParseLaO[selectionRope] ELSE {
first ¬ ParseLaO[selectionRope.Substr[len: ddLoc]];
last ¬ ParseLaO[selectionRope.Substr[start: ddLoc+2]]};
};
TEditInput.CloseEvent[];
ToSelectionRange[
viewer: viewer, lines: [first, last], skipCommentNodes: skipCommentNodes];
RETURN [recordAtom: TRUE, quit: TRUE];
EXITS
bad => {
MessageWindow.Append[
message: "Select int[\",\"int][\"..\"int[\",\"int]].", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN [recordAtom: FALSE, quit: FALSE];
};
};
NatFromRope: PROC [rope: Rope.ROPE] RETURNS [n: INT] ~ {
n ¬ Convert.IntFromRope[rope];
IF n<0 THEN {
MessageWindow.Append[
message: "Line numbers and offsets must be non-negative",
clearFirst: TRUE];
ERROR Convert.Error[overflow, 0]};
RETURN};
MessageLineOp: PRIVATE TEditInputExtras.CommandClosureProc ~ {
PROC [data: REF ANY, viewer: ViewerClasses.Viewer ← NIL, param: REFNIL]
RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE]
Print the line number of the input focus in the message window.
skipCommentNodes: BOOLEAN ~ NARROW[data, REF BOOLEAN]­;
selectionStart: TiogaOps.Location;
selectionEnd: TiogaOps.Location;
startPos, endPos: LineNumber.Position;
caretBefore: BOOLEAN;
root: TextNode.Ref ¬ NIL;
lines: LineNumber.LaORange;
[start: selectionStart, end: selectionEnd, caretBefore: caretBefore] ¬
TiogaOps.GetSelection[which: primary];
startPos ¬ TextNode.LocNumber[at: [selectionStart.node, selectionStart.where], skipCommentNodes: skipCommentNodes];
endPos ¬ TextNode.LocNumber[at: [selectionEnd.node, selectionEnd.where], skipCommentNodes: skipCommentNodes];
root ¬ TextNode.Root[selectionStart.node];
lines ¬ PositionsToLines[
branch: root, positions: [startPos, endPos], skipCommentNodes: skipCommentNodes];
TEditInput.CloseEvent[];
Save in global state for StuffLine.
previousLine ¬ IO.PutFLR["%g,%g..%g,%g (%g comment nodes)", LIST [[integer[lines.first.line]], [integer[lines.first.offset]], [integer[lines.last.line]], [integer[lines.last.offset]], [rope[IF skipCommentNodes THEN "excluding" ELSE "including"]] ]];
MessageWindow.Append[message: "Current line is ", clearFirst: TRUE];
MessageWindow.Append[message: previousLine, clearFirst: FALSE];
RETURN [recordAtom: TRUE, quit: TRUE];
};
StuffLineOp: PRIVATE TEditInputExtras.CommandClosureProc ~ {
PROC [data: REF ANY, viewer: ViewerClasses.Viewer ← NIL, param: REFNIL]
RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE]
Stuff the previous line number into the input focus.
This is completely stolen (sigh) from EditorComfortablePositioning.StuffPosition, except I use the previous line instead of the previous position.
Doit: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] = {
loc: TextNode.Location = SELECT tSel.insertion FROM before => tSel.start.pos, after => tSel.end.pos, ENDCASE => ERROR;
node: TextNode.Ref = loc.node;
nodeSize: INT;
where: INT ¬ loc.where;
resultStart, resultLen: INT ¬ 0;
IF node = NIL THEN GOTO Bad;
nodeSize ¬ TextEdit.Size[node];
where ¬ MIN[where, nodeSize];
IF where = TextNode.NodeItself
THEN where ¬ SELECT tSel.insertion FROM before => 0, after => nodeSize, ENDCASE => ERROR
ELSE IF tSel.insertion = after AND tSel.granularity # point THEN where ¬ where + 1;
TEditSelection.Deselect[primary];
[resultStart, resultLen] ¬ TextEdit.ReplaceByRope[
root: root,
dest: node,
rope: previousLine,
start: where,
len: 0,
looks: tSel.looks,
event: TEditInput.currentEvent];
tSel.start.pos.node ¬ tSel.end.pos.node ¬ node;
tSel.start.pos.where ¬ resultStart;
tSel.end.pos.where ¬ resultStart + resultLen - 1;
tSel.granularity ¬ char;
tSel.pendingDelete ¬ FALSE;
TEditSelection.MakeSelection[selection: primary, new: tSel];
EXITS Bad => TEditInputOps.EditFailed[]
};
TEditInputOps.CallWithLocks[Doit, TEditLocks.Access.write];
RETURN [recordAtom: TRUE, quit: TRUE];
};
RegisterWithTioga: PRIVATE PROCEDURE [] RETURNS [] ~ {
TEditInputExtras.RegisterClosure[
commandClosure: [
name: $ShowLine,
proc: ShowLineOp,
data: NEW[BOOLEAN ¬ FALSE]],
before: TRUE];
TEditInputExtras.RegisterClosure[
commandClosure: [
name: $ShowLineWithoutComments,
proc: ShowLineOp,
data: NEW[BOOLEAN ¬ TRUE]],
before: TRUE];
TEditInputExtras.RegisterClosure[
commandClosure: [
name: $MessageLine,
proc: MessageLineOp,
data: NEW[BOOLEAN ¬ FALSE]],
before: TRUE];
TEditInputExtras.RegisterClosure[
commandClosure: [
name: $MessageLineWithoutComments,
proc: MessageLineOp,
data: NEW[BOOLEAN ¬ TRUE]],
before: TRUE];
TEditInputExtras.RegisterClosure[
commandClosure: [
name: $StuffLine,
proc: StuffLineOp,
data: NIL],
before: TRUE];
};
TiogaLineNumberButtonProc: Commander.CommandProc ~ {
[cmd: Commander.Handle] RETURNS [result: REF ANYNIL, msg: ROPENIL]
placesMenu: Menus.Menu ~ Menus.MenuFromMenuEntry[
entryList: TEditDocumentPrivate.findMenu];
RepaintTiogaViewerMenu: ViewerOps.EnumProc ~ {
PROC [v: Viewer] RETURNS [BOOLTRUE]
IF v.class.flavor = $Text AND NOT v.iconic THEN {
ViewerOps.PaintViewer[
viewer: v, hint: ViewerOps.PaintHint.menu, clearClient: FALSE];
};
RETURN [TRUE];
};
AppendMenuButton[menu: placesMenu, line: 0];
ViewerOps.EnumerateViewers[enum: RepaintTiogaViewerMenu];
RETURN [result: NIL, msg: NIL];
};
Miscellaneous procedures.
NewLine: PRIVATE PROCEDURE [char: CHAR] RETURNS [BOOL] ~ INLINE {
RETURN [char = Ascii.CR OR char = Ascii.LF]
};
NodeActionProc: PRIVATE TYPE ~
PROCEDURE [node: TextNode.Ref] RETURNS [quit: BOOLEAN ¬ FALSE];
TreeWalkMap: PRIVATE PROCEDURE [
branch: TextNode.Ref,
skipCommentNodes: BOOLEAN ¬ FALSE,
action: NodeActionProc]
RETURNS [quit: BOOLEAN ¬ FALSE] ~ {
Given a node and an action, map the action to each of the nodes in standard tree walk order. If any action returns quit=TRUE, this immediately returns quit=TRUE, else, this returns when the tree walk ends with quit=FALSE.
root: TextNode.Ref ~ TextNode.Root[branch];
level, dl: INT ¬ 0;
node: TextNode.Ref ¬ branch;
WHILE node#NIL DO
IF node=root OR (node.comment AND skipCommentNodes)
THEN NULL
ELSE IF action[node: node] THEN RETURN [TRUE];
[node, dl] ¬ TextNode.Forward[node];
level ¬ level+dl;
IF level<=0 THEN RETURN [FALSE];
ENDLOOP;
RETURN [FALSE]};
ViewerOpsGetMenu: PRIVATE PROCEDURE [viewer: ViewerClasses.Viewer]
RETURNS [menu: Menus.Menu ¬ NIL] ~ {
Procedural accessor. Sigh.
RETURN [menu: viewer.menu];
};
Initialization.
Commander.Register[
key: "TiogaLineNumberButton",
proc: TiogaLineNumberButtonProc,
doc: "adds a Line button to the Places menu"];
RegisterWithTioga[];
}.