1 /*******************************************************************************
2 *									       *
3 * textDrag.c - Text Dragging routines for NEdit text widget		       *
4 *									       *
5 * Copyright (C) 1999 Mark Edel						       *
6 *									       *
7 * This is free software; you can redistribute it and/or modify it under the    *
8 * terms of the GNU General Public License as published by the Free Software    *
9 * Foundation; either version 2 of the License, or (at your option) any later   *
10 * version. In addition, you may distribute version of this program linked to   *
11 * Motif or Open Motif. See README for details.                                 *
12 * 									       *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
15 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License        *
16 * for more details.							       *
17 * 									       *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple     *
20 * Place, Suite 330, Boston, MA  02111-1307 USA		                       *
21 *									       *
22 * Nirvana Text Editor	    						       *
23 * Dec. 15, 1995								       *
24 *									       *
25 * Written by Mark Edel							       *
26 *									       *
27 *******************************************************************************/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32 
33 #include "textDrag.h"
34 #include "textBuf.h"
35 #include "textDisp.h"
36 #include "textP.h"
37 #include "../util/nedit_malloc.h"
38 
39 #include <limits.h>
40 
41 #include <X11/Intrinsic.h>
42 #include <X11/IntrinsicP.h>
43 #include <Xm/Xm.h>
44 #include <Xm/XmP.h>
45 #if XmVersion >= 1002
46 #include <Xm/PrimitiveP.h>
47 #endif
48 
49 #ifdef HAVE_DEBUG_H
50 #include "../debug.h"
51 #endif
52 
53 static void trackModifyRange(int *rangeStart, int *modRangeEnd,
54     	int *unmodRangeEnd, int modPos, int nInserted, int nDeleted);
55 static void findTextMargins(textBuffer *buf, int start, int end, int *leftMargin,
56     	int *rightMargin);
57 static int findRelativeLineStart(textBuffer *buf, int referencePos,
58     	int referenceLineNum, int newLineNum);
59 static int min3(int i1, int i2, int i3);
60 static int max3(int i1, int i2, int i3);
61 static int max(int i1, int i2);
62 
63 /*
64 ** Start the process of dragging the current primary-selected text across
65 ** the window (move by dragging, as opposed to dragging to create the
66 ** selection)
67 */
BeginBlockDrag(TextWidget tw)68 void BeginBlockDrag(TextWidget tw)
69 {
70     textDisp *textD = tw->text.textD;
71     textBuffer *buf = textD->buffer;
72     int fontHeight = textD->fontStruct->ascent + textD->fontStruct->descent;
73     int fontWidth = textD->fontStruct->max_bounds.width;
74     selection *sel = &buf->primary;
75     int nLines, mousePos, lineStart;
76     int x, y, lineEnd;
77 
78     char *text;
79 
80     /* Save a copy of the whole text buffer as a backup, and for
81        deriving changes */
82     tw->text.dragOrigBuf = BufCreate();
83     BufSetTabDistance(tw->text.dragOrigBuf, buf->tabDist);
84     tw->text.dragOrigBuf->useTabs = buf->useTabs;
85     text = BufGetAll(buf);
86     BufSetAll(tw->text.dragOrigBuf, text);
87     NEditFree(text);
88     if (sel->rectangular)
89     	BufRectSelect(tw->text.dragOrigBuf, sel->start, sel->end, sel->rectStart,
90     	    	sel->rectEnd);
91     else
92     	BufSelect(tw->text.dragOrigBuf, sel->start, sel->end);
93 
94     /* Record the mouse pointer offsets from the top left corner of the
95        selection (the position where text will actually be inserted In dragging
96        non-rectangular selections)  */
97     if (sel->rectangular) {
98     	tw->text.dragXOffset = tw->text.btnDownX + textD->horizOffset -
99     	    	textD->left - sel->rectStart * fontWidth;
100     } else {
101         if (!TextDPositionToXY(textD, sel->start, &x, &y))
102             x = BufCountDispChars(buf, TextDStartOfLine(textD, sel->start),
103             	    sel->start) * fontWidth + textD->left -
104             	    textD->horizOffset;
105         tw->text.dragXOffset = tw->text.btnDownX - x;
106     }
107     mousePos = TextDXYToPosition(textD, tw->text.btnDownX, tw->text.btnDownY);
108     nLines = BufCountLines(buf, sel->start, mousePos);
109     tw->text.dragYOffset = nLines * fontHeight + (((tw->text.btnDownY -
110     	    tw->text.marginHeight) % fontHeight) - fontHeight/2);
111     tw->text.dragNLines = BufCountLines(buf, sel->start, sel->end);
112 
113     /* Record the current drag insert position and the information for
114        undoing the fictional insert of the selection in its new position */
115     tw->text.dragInsertPos = sel->start;
116     tw->text.dragInserted = sel->end - sel->start;
117     if (sel->rectangular) {
118     	textBuffer *testBuf = BufCreate();
119     	char *testText = BufGetRange(buf, sel->start, sel->end);
120         BufSetTabDistance(testBuf, buf->tabDist);
121         testBuf->useTabs = buf->useTabs;
122     	BufSetAll(testBuf, testText);
123     	NEditFree(testText);
124     	BufRemoveRect(testBuf, 0, sel->end - sel->start, sel->rectStart,
125     	    	sel->rectEnd);
126     	tw->text.dragDeleted = testBuf->length;
127     	BufFree(testBuf);
128     	tw->text.dragRectStart = sel->rectStart;
129     } else {
130     	tw->text.dragDeleted = 0;
131     	tw->text.dragRectStart = 0;
132     }
133     tw->text.dragType = DRAG_MOVE;
134     tw->text.dragSourceDeletePos = sel->start;
135     tw->text.dragSourceInserted = tw->text.dragDeleted;
136     tw->text.dragSourceDeleted = tw->text.dragInserted;
137 
138     /* For non-rectangular selections, fill in the rectangular information in
139        the selection for overlay mode drags which are done rectangularly */
140     if (!sel->rectangular) {
141     	lineStart = BufStartOfLine(buf, sel->start);
142     	if (tw->text.dragNLines == 0) {
143     	    tw->text.dragOrigBuf->primary.rectStart =
144     	    	    BufCountDispChars(buf, lineStart, sel->start);
145     	    tw->text.dragOrigBuf->primary.rectEnd =
146     	    	    BufCountDispChars(buf, lineStart, sel->end);
147     	} else {
148     	    lineEnd = BufGetCharacter(buf, sel->end - 1) == '\n' ?
149     	    	    sel->end - 1 : sel->end;
150     	    findTextMargins(buf, lineStart, lineEnd,
151     	    	    &tw->text.dragOrigBuf->primary.rectStart,
152     	    	    &tw->text.dragOrigBuf->primary.rectEnd);
153     	}
154     }
155 
156     /* Set the drag state to announce an ongoing block-drag */
157     tw->text.dragState = PRIMARY_BLOCK_DRAG;
158 
159     /* Call the callback announcing the start of a block drag */
160     XtCallCallbacks((Widget)tw, textNdragStartCallback, (XtPointer)NULL);
161 }
162 
163 /*
164 ** Reposition the primary-selected text that is being dragged as a block
165 ** for a new mouse position of (x, y)
166 */
BlockDragSelection(TextWidget tw,int x,int y,int dragType)167 void BlockDragSelection(TextWidget tw, int x, int y, int dragType)
168 {
169     textDisp *textD = tw->text.textD;
170     textBuffer *buf = textD->buffer;
171     int fontHeight = textD->fontStruct->ascent + textD->fontStruct->descent;
172     int fontWidth = textD->fontStruct->max_bounds.width;
173     textBuffer *origBuf = tw->text.dragOrigBuf;
174     int dragXOffset = tw->text.dragXOffset;
175     textBuffer *tempBuf;
176     selection *origSel = &origBuf->primary;
177     int rectangular = origSel->rectangular;
178     int overlay, oldDragType = tw->text.dragType;
179     int nLines = tw->text.dragNLines;
180     int insLineNum, insLineStart, insRectStart, insRectEnd, insStart;
181     char *repText, *text, *insText;
182     int modRangeStart = -1, tempModRangeEnd = -1, bufModRangeEnd = -1;
183     int referenceLine, referencePos, tempStart, tempEnd, origSelLen;
184     int insertInserted, insertDeleted, row, column;
185     int origSelLineStart, origSelLineEnd;
186     int sourceInserted, sourceDeleted, sourceDeletePos;
187 
188     if (tw->text.dragState != PRIMARY_BLOCK_DRAG)
189     	return;
190 
191     /* The operation of block dragging is simple in theory, but not so simple
192        in practice.  There is a backup buffer (tw->text.dragOrigBuf) which
193        holds a copy of the buffer as it existed before the drag.  When the
194        user drags the mouse to a new location, this routine is called, and
195        a temporary buffer is created and loaded with the local part of the
196        buffer (from the backup) which might be changed by the drag.  The
197        changes are all made to this temporary buffer, and the parts of this
198        buffer which then differ from the real (displayed) buffer are used to
199        replace those parts, thus one replace operation serves as both undo
200        and modify.  This double-buffering of the operation prevents excessive
201        redrawing (though there is still plenty of needless redrawing due to
202        re-selection and rectangular operations).
203 
204        The hard part is keeping track of the changes such that a single replace
205        operation will do everyting.  This is done using a routine called
206        trackModifyRange which tracks expanding ranges of changes in the two
207        buffers in modRangeStart, tempModRangeEnd, and bufModRangeEnd. */
208 
209     /* Create a temporary buffer for accumulating changes which will
210        eventually be replaced in the real buffer.  Load the buffer with the
211        range of characters which might be modified in this drag step
212        (this could be tighter, but hopefully it's not too slow) */
213     tempBuf = BufCreate();
214     tempBuf->tabDist = buf->tabDist;
215     tempBuf->useTabs = buf->useTabs;
216     tempStart = min3(tw->text.dragInsertPos, origSel->start,
217     	 BufCountBackwardNLines(buf, textD->firstChar, nLines+2));
218     tempEnd = BufCountForwardNLines(buf, max3(tw->text.dragInsertPos,
219     	 origSel->start, textD->lastChar), nLines+2) +
220     	 origSel->end - origSel->start;
221     text = BufGetRange(origBuf, tempStart, tempEnd);
222     BufSetAll(tempBuf, text);
223     NEditFree(text);
224 
225     /* If the drag type is USE_LAST, use the last dragType applied */
226     if (dragType == USE_LAST)
227     	dragType = tw->text.dragType;
228     overlay = dragType == DRAG_OVERLAY_MOVE || dragType == DRAG_OVERLAY_COPY;
229 
230     /* Overlay mode uses rectangular selections whether or not the original
231        was rectangular.  To use a plain selection as if it were rectangular,
232        the start and end positions need to be moved to the line boundaries
233        and trailing newlines must be excluded */
234     origSelLineStart = BufStartOfLine(origBuf, origSel->start);
235     if (!rectangular && BufGetCharacter(origBuf, origSel->end - 1) == '\n')
236     	origSelLineEnd = origSel->end - 1;
237     else
238     	origSelLineEnd = BufEndOfLine(origBuf, origSel->end);
239     if (!rectangular && overlay && nLines != 0)
240     	dragXOffset -= fontWidth * (origSel->rectStart -
241     	    	(origSel->start - origSelLineStart));
242 
243     /* If the drag operation is of a different type than the last one, and the
244        operation is a move, expand the modified-range to include undoing the
245        text-removal at the site from which the text was dragged. */
246     if (dragType != oldDragType && tw->text.dragSourceDeleted != 0)
247     	trackModifyRange(&modRangeStart, &bufModRangeEnd, &tempModRangeEnd,
248     	    	tw->text.dragSourceDeletePos, tw->text.dragSourceInserted,
249     	    	tw->text.dragSourceDeleted);
250 
251     /* Do, or re-do the original text removal at the site where a move began.
252        If this part has not changed from the last call, do it silently to
253        bring the temporary buffer in sync with the real (displayed)
254        buffer.  If it's being re-done, track the changes to complete the
255        redo operation begun above */
256     if (dragType == DRAG_MOVE || dragType == DRAG_OVERLAY_MOVE) {
257 	if (rectangular || overlay) {
258     	    int prevLen = tempBuf->length;
259     	    origSelLen = origSelLineEnd - origSelLineStart;
260     	    if (overlay)
261     		BufClearRect(tempBuf, origSelLineStart-tempStart,
262     		    	origSelLineEnd-tempStart, origSel->rectStart,
263     		    	origSel->rectEnd);
264     	    else
265     		BufRemoveRect(tempBuf, origSelLineStart-tempStart,
266     		    	origSelLineEnd-tempStart, origSel->rectStart,
267     		    	origSel->rectEnd);
268     	    sourceDeletePos = origSelLineStart;
269     	    sourceInserted = origSelLen - prevLen + tempBuf->length;
270     	    sourceDeleted = origSelLen;
271 	} else {
272     	    BufRemove(tempBuf, origSel->start - tempStart,
273     	    	    origSel->end - tempStart);
274     	    sourceDeletePos = origSel->start;
275     	    sourceInserted = 0;
276     	    sourceDeleted = origSel->end - origSel->start;
277 	}
278 	if (dragType != oldDragType)
279     	    trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
280     	    	    sourceDeletePos, sourceInserted, sourceDeleted);
281     } else {
282     	sourceDeletePos = 0;
283     	sourceInserted = 0;
284     	sourceDeleted = 0;
285     }
286 
287     /* Expand the modified-range to include undoing the insert from the last
288        call. */
289     trackModifyRange(&modRangeStart, &bufModRangeEnd, &tempModRangeEnd,
290     	   tw->text.dragInsertPos, tw->text.dragInserted, tw->text.dragDeleted);
291 
292     /* Find the line number and column of the insert position.  Note that in
293        continuous wrap mode, these must be calculated as if the text were
294        not wrapped */
295     TextDXYToUnconstrainedPosition(textD, max(0, x - dragXOffset),
296     	    max(0, y - (tw->text.dragYOffset % fontHeight)), &row, &column);
297     column = TextDOffsetWrappedColumn(textD, row, column);
298     row = TextDOffsetWrappedRow(textD, row);
299     insLineNum = row + textD->topLineNum - tw->text.dragYOffset / fontHeight;
300 
301     /* find a common point of reference between the two buffers, from which
302        the insert position line number can be translated to a position */
303     if (textD->firstChar > modRangeStart) {
304     	referenceLine = textD->topLineNum -
305     	    	BufCountLines(buf, modRangeStart, textD->firstChar);
306     	referencePos = modRangeStart;
307     } else {
308     	referencePos = textD->firstChar;
309     	referenceLine = textD->topLineNum;
310     }
311 
312     /* find the position associated with the start of the new line in the
313        temporary buffer */
314     insLineStart = findRelativeLineStart(tempBuf, referencePos - tempStart,
315     	    referenceLine, insLineNum) + tempStart;
316     if (insLineStart - tempStart == tempBuf->length)
317     	insLineStart = BufStartOfLine(tempBuf, insLineStart - tempStart) +
318     	    	tempStart;
319 
320     /* Find the actual insert position */
321     if (rectangular || overlay) {
322     	insStart = insLineStart;
323     	insRectStart = column;
324     } else { /* note, this will fail with proportional fonts */
325     	insStart = BufCountForwardDispChars(tempBuf, insLineStart - tempStart,
326     	    	column) + tempStart;
327     	insRectStart = 0;
328     }
329 
330     /* If the position is the same as last time, don't bother drawing (it
331        would be nice if this decision could be made earlier) */
332     if (insStart == tw->text.dragInsertPos &&
333     	    insRectStart == tw->text.dragRectStart && dragType == oldDragType) {
334     	BufFree(tempBuf);
335     	return;
336     }
337 
338     /* Do the insert in the temporary buffer */
339     if (rectangular || overlay) {
340     	insText = BufGetTextInRect(origBuf, origSelLineStart, origSelLineEnd,
341     	    	origSel->rectStart, origSel->rectEnd);
342     	if (overlay)
343     	    BufOverlayRect(tempBuf, insStart - tempStart, insRectStart,
344     	    	    insRectStart + origSel->rectEnd - origSel->rectStart,
345     	    	    insText, &insertInserted, &insertDeleted);
346     	else
347     	    BufInsertCol(tempBuf, insRectStart, insStart - tempStart, insText,
348     	    	    &insertInserted, &insertDeleted);
349     	trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
350     	    	insStart, insertInserted, insertDeleted);
351     	NEditFree(insText);
352     } else {
353     	insText = BufGetSelectionText(origBuf);
354     	BufInsert(tempBuf, insStart - tempStart, insText);
355     	trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
356     	    	    insStart, origSel->end - origSel->start, 0);
357     	insertInserted = origSel->end - origSel->start;
358     	insertDeleted = 0;
359     	NEditFree(insText);
360     }
361 
362     /* Make the changes in the real buffer */
363     repText = BufGetRange(tempBuf, modRangeStart - tempStart,
364     	    tempModRangeEnd - tempStart);
365     BufFree(tempBuf);
366     TextDBlankCursor(textD);
367     BufReplace(buf, modRangeStart, bufModRangeEnd, repText);
368     NEditFree(repText);
369 
370     /* Store the necessary information for undoing this step */
371     tw->text.dragInsertPos = insStart;
372     tw->text.dragRectStart = insRectStart;
373     tw->text.dragInserted = insertInserted;
374     tw->text.dragDeleted = insertDeleted;
375     tw->text.dragSourceDeletePos = sourceDeletePos;
376     tw->text.dragSourceInserted = sourceInserted;
377     tw->text.dragSourceDeleted = sourceDeleted;
378     tw->text.dragType = dragType;
379 
380     /* Reset the selection and cursor position */
381     if (rectangular || overlay) {
382     	insRectEnd = insRectStart + origSel->rectEnd - origSel->rectStart;
383     	BufRectSelect(buf, insStart, insStart + insertInserted, insRectStart,
384     	    	insRectEnd);
385     	TextDSetInsertPosition(textD, BufCountForwardDispChars(buf,
386     	    	BufCountForwardNLines(buf, insStart, tw->text.dragNLines),
387     	    	insRectEnd));
388     } else {
389     	BufSelect(buf, insStart, insStart + origSel->end - origSel->start);
390     	TextDSetInsertPosition(textD, insStart + origSel->end - origSel->start);
391     }
392     TextDUnblankCursor(textD);
393     XtCallCallbacks((Widget)tw, textNcursorMovementCallback, (XtPointer)NULL);
394     tw->text.emTabsBeforeCursor = 0;
395 }
396 
397 /*
398 ** Complete a block text drag operation
399 */
FinishBlockDrag(TextWidget tw)400 void FinishBlockDrag(TextWidget tw)
401 {
402     dragEndCBStruct endStruct;
403     int modRangeStart = -1, origModRangeEnd, bufModRangeEnd;
404     char *deletedText;
405 
406     /* Find the changed region of the buffer, covering both the deletion
407        of the selected text at the drag start position, and insertion at
408        the drag destination */
409     trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
410     	    	tw->text.dragSourceDeletePos, tw->text.dragSourceInserted,
411     	    	tw->text.dragSourceDeleted);
412     trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
413     	    	tw->text.dragInsertPos, tw->text.dragInserted,
414     	    	tw->text.dragDeleted);
415 
416     /* Get the original (pre-modified) range of text from saved backup buffer */
417     deletedText = BufGetRange(tw->text.dragOrigBuf, modRangeStart,
418     	    origModRangeEnd);
419 
420     /* Free the backup buffer */
421     BufFree(tw->text.dragOrigBuf);
422 
423     /* Return to normal drag state */
424     tw->text.dragState = NOT_CLICKED;
425 
426     /* Call finish-drag calback */
427     endStruct.startPos = modRangeStart;
428     endStruct.nCharsDeleted = origModRangeEnd - modRangeStart;
429     endStruct.nCharsInserted = bufModRangeEnd - modRangeStart;
430     endStruct.deletedText = deletedText;
431     XtCallCallbacks((Widget)tw, textNdragEndCallback, (XtPointer)&endStruct);
432     NEditFree(deletedText);
433 }
434 
435 /*
436 ** Cancel a block drag operation
437 */
CancelBlockDrag(TextWidget tw)438 void CancelBlockDrag(TextWidget tw)
439 {
440     textBuffer *buf = tw->text.textD->buffer;
441     textBuffer *origBuf = tw->text.dragOrigBuf;
442     selection *origSel = &origBuf->primary;
443     int modRangeStart = -1, origModRangeEnd, bufModRangeEnd;
444     char *repText;
445     dragEndCBStruct endStruct;
446 
447     /* If the operation was a move, make the modify range reflect the
448        removal of the text from the starting position */
449     if (tw->text.dragSourceDeleted != 0)
450     	trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
451     	    	tw->text.dragSourceDeletePos, tw->text.dragSourceInserted,
452     	    	tw->text.dragSourceDeleted);
453 
454     /* Include the insert being undone from the last step in the modified
455        range. */
456     trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
457     	   tw->text.dragInsertPos, tw->text.dragInserted, tw->text.dragDeleted);
458 
459     /* Make the changes in the buffer */
460     repText = BufGetRange(origBuf, modRangeStart, origModRangeEnd);
461     BufReplace(buf, modRangeStart, bufModRangeEnd, repText);
462     NEditFree(repText);
463 
464     /* Reset the selection and cursor position */
465     if (origSel->rectangular)
466     	BufRectSelect(buf, origSel->start, origSel->end, origSel->rectStart,
467     	    	origSel->rectEnd);
468     else
469     	BufSelect(buf, origSel->start, origSel->end);
470     TextDSetInsertPosition(tw->text.textD, buf->cursorPosHint);
471     XtCallCallbacks((Widget)tw, textNcursorMovementCallback, NULL);
472     tw->text.emTabsBeforeCursor = 0;
473 
474     /* Free the backup buffer */
475     BufFree(origBuf);
476 
477     /* Indicate end of drag */
478     tw->text.dragState = DRAG_CANCELED;
479 
480     /* Call finish-drag calback */
481     endStruct.startPos = 0;
482     endStruct.nCharsDeleted = 0;
483     endStruct.nCharsInserted = 0;
484     endStruct.deletedText = NULL;
485     XtCallCallbacks((Widget)tw, textNdragEndCallback, (XtPointer)&endStruct);
486 }
487 
488 /*
489 ** Maintain boundaries of changed region between two buffers which
490 ** start out with identical contents, but diverge through insertion,
491 ** deletion, and replacement, such that the buffers can be reconciled
492 ** by replacing the changed region of either buffer with the changed
493 ** region of the other.
494 **
495 ** rangeStart is the beginning of the modification region in the shared
496 ** coordinates of both buffers (which are identical up to rangeStart).
497 ** modRangeEnd is the end of the changed region for the buffer being
498 ** modified, unmodRangeEnd is the end of the region for the buffer NOT
499 ** being modified.  A value of -1 in rangeStart indicates that there
500 ** have been no modifications so far.
501 */
trackModifyRange(int * rangeStart,int * modRangeEnd,int * unmodRangeEnd,int modPos,int nInserted,int nDeleted)502 static void trackModifyRange(int *rangeStart, int *modRangeEnd,
503     	int *unmodRangeEnd, int modPos, int nInserted, int nDeleted)
504 {
505     if (*rangeStart == -1) {
506     	*rangeStart = modPos;
507     	*modRangeEnd = modPos + nInserted;
508     	*unmodRangeEnd = modPos + nDeleted;
509     } else {
510     	if (modPos < *rangeStart)
511     	    *rangeStart = modPos;
512     	if (modPos + nDeleted > *modRangeEnd) {
513     	    *unmodRangeEnd += modPos + nDeleted - *modRangeEnd;
514     	    *modRangeEnd = modPos + nInserted;
515     	} else
516     	    *modRangeEnd += nInserted - nDeleted;
517     }
518 }
519 
520 /*
521 ** Find the left and right margins of text between "start" and "end" in
522 ** buffer "buf".  Note that "start is assumed to be at the start of a line.
523 */
findTextMargins(textBuffer * buf,int start,int end,int * leftMargin,int * rightMargin)524 static void findTextMargins(textBuffer *buf, int start, int end, int *leftMargin,
525     	int *rightMargin)
526 {
527     char c;
528     int pos, width = 0, maxWidth = 0, minWhite = INT_MAX, inWhite = True;
529 
530     for (pos=start; pos<end; pos++) {
531     	c = BufGetCharacter(buf, pos);
532     	if (inWhite && c != ' ' && c != '\t') {
533     	    inWhite = False;
534     	    if (width < minWhite)
535     	    	minWhite = width;
536     	}
537     	if (c == '\n') {
538     	    if (width > maxWidth)
539     	    	maxWidth = width;
540     	    width = 0;
541     	    inWhite = True;
542     	} else
543     	    width += BufCharWidth(c, width, buf->tabDist, buf->nullSubsChar);
544     }
545     if (width > maxWidth)
546     	maxWidth = width;
547     *leftMargin = minWhite == INT_MAX ? 0 : minWhite;
548     *rightMargin = maxWidth;
549 }
550 
551 /*
552 ** Find a text position in buffer "buf" by counting forward or backward
553 ** from a reference position with known line number
554 */
findRelativeLineStart(textBuffer * buf,int referencePos,int referenceLineNum,int newLineNum)555 static int findRelativeLineStart(textBuffer *buf, int referencePos,
556     	int referenceLineNum, int newLineNum)
557 {
558     if (newLineNum < referenceLineNum)
559     	return BufCountBackwardNLines(buf, referencePos,
560     	    	referenceLineNum - newLineNum);
561     else if (newLineNum > referenceLineNum)
562     	return BufCountForwardNLines(buf, referencePos,
563     	    	newLineNum - referenceLineNum);
564     return BufStartOfLine(buf, referencePos);
565 }
566 
min3(int i1,int i2,int i3)567 static int min3(int i1, int i2, int i3)
568 {
569     if (i1 <= i2 && i1 <= i3)
570     	return i1;
571     return i2 <= i3 ? i2 : i3;
572 }
573 
max3(int i1,int i2,int i3)574 static int max3(int i1, int i2, int i3)
575 {
576     if (i1 >= i2 && i1 >= i3)
577     	return i1;
578     return i2 >= i3 ? i2 : i3;
579 }
580 
max(int i1,int i2)581 static int max(int i1, int i2)
582 {
583     return i1 >= i2 ? i1 : i2;
584 }
585