    TK Soh's extension to tag navigation: tagstack

    This is a patch to add vim-like tagstack capability to NEdit. When you make
    a successful call to "Find  Definition", the current window position is
    pushed into a LIFO stack before make jump, where you can later return the
    position by popping the tagstack.

    http://sourceforge.net/tracker/?func=detail&aid=588380&group_id=11005&atid=311005
	    [ 588380 ] tagstack
    
Index: source/file.c
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/file.c,v
retrieving revision 1.88
diff -u -r1.88 file.c
--- source/file.c	24 Aug 2004 09:37:24 -0000	1.88
+++ source/file.c	26 Oct 2004 00:53:15 -0000
@@ -295,6 +295,7 @@
     }
     UpdateWindowTitle(window);
     UpdateWindowReadOnly(window);
+    RemoveTagStack(window);
     
     /* restore the insert and scroll positions of each pane */
     for (i=0; i<=window->nPanes; i++) {
Index: source/menu.c
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/menu.c,v
retrieving revision 1.115
diff -u -r1.115 menu.c
--- source/menu.c	15 Sep 2004 22:50:57 -0000	1.115
+++ source/menu.c	26 Oct 2004 00:53:16 -0000
@@ -110,6 +110,7 @@
 static void replaceFindSameCB(Widget w, XtPointer clientData, XtPointer callData);
 static void markCB(Widget w, XtPointer clientData, XtPointer callData);
 static void gotoMarkCB(Widget w, XtPointer clientData, XtPointer callData);
+static void findDefinitionCB(Widget w, XtPointer clientData, XtPointer callData);
 static void gotoMatchingCB(Widget w, XtPointer clientData, XtPointer callData);
 static void autoIndentOffCB(Widget w, WindowInfo *window, caddr_t callData);
 static void autoIndentCB(Widget w, WindowInfo *window, caddr_t callData);
@@ -295,6 +296,8 @@
 static void gotoMatchingAP(Widget w, XEvent *event, String *args,
 	Cardinal *nArgs);
 static void findDefAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 
+static void popTagStackAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 
+static void clearTagStackAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 
 static void showTipAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); 
 static void splitPaneAP(Widget w, XEvent *event, String *args,
 	Cardinal *nArgs);
@@ -512,6 +515,8 @@
     {"goto_matching", gotoMatchingAP},
     {"find-definition", findDefAP},
     {"find_definition", findDefAP},
+    {"pop_tagstack", popTagStackAP},
+    {"clear_tagstack", clearTagStackAP},
     {"show_tip", showTipAP},
     {"split-pane", splitPaneAP},
     {"split_pane", splitPaneAP},
@@ -799,9 +804,11 @@
     createMenuItem(menuPane, "gotoMatching", "Goto Matching (..)", 'M',
     	    gotoMatchingCB, window, FULL);
     createFakeMenuItem(menuPane, "gotoMatchingShift", gotoMatchingCB, window);
+    createMenuSeparator(menuPane, "sep1", FULL);
     window->findDefItem = createMenuItem(menuPane, "findDefinition",
-    	    "Find Definition", 'D', doActionCB, "find_definition", FULL);
+    	    "Find Definition", 'D', findDefinitionCB, window, FULL);
     XtSetSensitive(window->findDefItem, TagsFileList != NULL);
+    createFakeMenuItem(menuPane, "findDefinitionShift", findDefinitionCB, window);
     window->showTipItem = createMenuItem(menuPane, "showCalltip",
     	    "Show Calltip", 'C', doActionCB, "show_tip", FULL);
     XtSetSensitive(window->showTipItem, (TagsFileList != NULL || 
@@ -1492,6 +1499,16 @@
 		extend ? 1 : 0);
 }
 
+static void findDefinitionCB(Widget w, XtPointer clientData, XtPointer callData)
+{
+    HidePointerOnKeyedEvent(WidgetToWindow(MENU_WIDGET(w))->lastFocus,
+            ((XmAnyCallbackStruct *)callData)->event);
+    XtCallActionProc(WidgetToWindow(MENU_WIDGET(w))->lastFocus,
+    	    ((XmAnyCallbackStruct *)callData)->event->xbutton.state & ShiftMask
+    	    ? "pop_tagstack" : "find_definition",
+    	    ((XmAnyCallbackStruct *)callData)->event, NULL, 0);
+}
+
 static void gotoMatchingCB(Widget w, XtPointer clientData, XtPointer callData)
 {
     HidePointerOnKeyedEvent(WidgetToWindow(MENU_WIDGET(w))->lastFocus,
@@ -3420,6 +3437,26 @@
 {
     FindDefinition(WidgetToWindow(w), event->xbutton.time,
 	    *nArgs == 0 ? NULL : args[0]);
+}
+
+static void popTagStackAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) 
+{
+    int count = 1;
+    
+    if (*nArgs) {
+        if (sscanf(args[0], "%d", &count) != 1 || count < 1) {
+            fprintf(stderr,
+                "NEdit: pop_tagstack requires integer argument > 0\n");
+	    return;
+	}
+    }
+    	
+    PopTagStack(WidgetToWindow(w), 1);
+}
+
+static void clearTagStackAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) 
+{
+    ClearTagStack();
 }
 
 static void showTipAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) 
Index: source/nedit.c
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/nedit.c,v
retrieving revision 1.83
diff -u -r1.83 nedit.c
--- source/nedit.c	2 Sep 2004 08:49:56 -0000	1.83
+++ source/nedit.c	26 Oct 2004 00:53:16 -0000
@@ -311,7 +311,8 @@
     "*searchMenu.gotoMatching.acceleratorText: [Shift]Ctrl+M",
     "*searchMenu.gotoMatchingShift.accelerator: Shift Ctrl<Key>m",
     "*searchMenu.findDefinition.accelerator: Ctrl<Key>d",
-    "*searchMenu.findDefinition.acceleratorText: Ctrl+D",
+    "*searchMenu.findDefinition.acceleratorText: [Shift]Ctrl+D",
+    "*searchMenu.findDefinitionShift.accelerator: Shift Ctrl<Key>d",
     "*searchMenu.showCalltip.accelerator: Ctrl<Key>apostrophe",
     "*searchMenu.showCalltip.acceleratorText: Ctrl+'",    
     "*preferencesMenu.mnemonic: P",
Index: source/selection.c
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/selection.c,v
retrieving revision 1.31
diff -u -r1.31 selection.c
--- source/selection.c	1 Aug 2004 10:06:11 -0000	1.31
+++ source/selection.c	26 Oct 2004 00:53:16 -0000
@@ -80,9 +80,9 @@
     	Boolean *continueDispatch);
 static void gotoMarkExtendKeyCB(Widget w, XtPointer clientData, XEvent *event,
     	Boolean *continueDispatch);
-static void maintainSelection(selection *sel, int pos, int nInserted,
+void maintainSelection(selection *sel, int pos, int nInserted,
     	int nDeleted);
-static void maintainPosition(int *position, int modPos, int nInserted,
+void maintainPosition(int *position, int modPos, int nInserted,
     	int nDeleted);
 
 /*
@@ -653,7 +653,7 @@
 ** Update a selection across buffer modifications specified by
 ** "pos", "nDeleted", and "nInserted".
 */
-static void maintainSelection(selection *sel, int pos, int nInserted,
+void maintainSelection(selection *sel, int pos, int nInserted,
 	int nDeleted)
 {
     if (!sel->selected || pos > sel->end)
@@ -668,7 +668,7 @@
 ** Update a position across buffer modifications specified by
 ** "modPos", "nDeleted", and "nInserted".
 */
-static void maintainPosition(int *position, int modPos, int nInserted,
+void maintainPosition(int *position, int modPos, int nInserted,
     	int nDeleted)
 {
     if (modPos > *position)
Index: source/tags.c
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/tags.c,v
retrieving revision 1.59
diff -u -r1.59 tags.c
--- source/tags.c	1 Aug 2004 10:06:11 -0000	1.59
+++ source/tags.c	26 Oct 2004 00:53:16 -0000
@@ -101,6 +101,12 @@
 
 enum searchDirection {FORWARD, BACKWARD};
 
+void maintainSelection(selection *sel, int pos, int nInserted,
+    	int nDeleted);
+void maintainPosition(int *position, int modPos, int nInserted,
+    	int nDeleted);
+static void pushTagStack(tagStackData tagData);
+static tagStackData getTagStackData(WindowInfo *window);
 static int loadTagsFile(const char *tagSpec, int index, int recLevel);
 static void findDefCB(Widget widget, WindowInfo *window, Atom *sel,
         Atom *type, char *value, int *length, int *format);
@@ -144,6 +150,8 @@
 static tag **Tips = NULL;
 tagFile *TipsFileList = NULL;
 
+tagStackData TagStack[TAG_STACK_DEPTH];  /* stack of tag callers */
+int TagStackIndex;    /* stack pointer of tagstack */
 
 /* These are all transient global variables -- they don't hold any state
     between tag/tip lookups */
@@ -1429,7 +1437,11 @@
     char filename[MAXPATHLEN], pathname[MAXPATHLEN];
     WindowInfo *windowToSearch;
     WindowInfo *parentWindow = WidgetToWindow(parent);
-    
+    tagStackData tagData;
+
+    /* save current selection for tagstack */
+    tagData = getTagStackData(WidgetToWindow(parent));
+
     ParseFilename(tagFiles[i],filename,pathname);
     /* open the file containing the definition */
     EditExistingFile(parentWindow, filename, pathname, 0, NULL, False, 
@@ -1446,6 +1458,7 @@
     if(!*(tagSearch[i])) {
         /* if the search string is empty, select the numbered line */
         SelectNumberedLine(windowToSearch, startPos);
+        pushTagStack(tagData);
         return;
     }
 
@@ -1458,6 +1471,8 @@
         return;
     }
 
+    pushTagStack(tagData);
+    
     /* select the matched string */
     BufSelect(windowToSearch->buffer, startPos, endPos);
 
@@ -2059,4 +2074,149 @@
     }
     free_alias_list(aliases);
     return nTipsAdded;
+}
+
+void ClearTagStack(void)
+{
+    TagStackIndex = 0;
+}
+
+/*
+** Remove tagstack entries belong to the window specified,
+** mainly because the window is about to be removed.
+*/
+void RemoveTagStack(WindowInfo *window)
+{
+    int i = 0;
+    
+    while(i<TagStackIndex) {
+        if (TagStack[i].window == window) {
+	    memcpy(&TagStack[i], &TagStack[i+1],
+	            sizeof(tagStackData)*(TagStackIndex-i-1));
+		    
+	    TagStackIndex--;
+	}
+	else
+	    i++;
+    }
+}
+
+/*
+** gather the cursor/selection info for tagstack's use
+*/
+static tagStackData getTagStackData(WindowInfo *window)
+{
+    tagStackData tagData;
+    
+    tagData.window = window;
+    tagData.textPane = window->lastFocus;
+    tagData.cursorPos = TextGetCursorPos(window->lastFocus);
+    memcpy(&tagData.sel, &window->buffer->primary,
+    	    sizeof(selection));
+    
+    return tagData;
+}
+
+/* 
+** push the last caller to find-definition on top of tagstack
+*/
+static void pushTagStack(tagStackData tagData)
+{
+    /* shift out oldest caller if tagstack is full */
+    if (TagStackIndex >= TAG_STACK_DEPTH) {
+        int i;
+	for (i=0; i<TagStackIndex; i++) {
+	    memcpy(&TagStack[i],&TagStack[i+1],sizeof(tagStackData));
+	}
+	TagStackIndex--;
+    }
+    
+    /* save tag caller info */
+    TagStack[TagStackIndex] = tagData;
+
+    /* move pointer to next available slot */
+    TagStackIndex++;
+}
+
+/*
+** Go to last window-line that made successfull call to find-definition 
+** (at the top of tag stack), and pop it from the tag stack.
+*/
+void PopTagStack(WindowInfo *window, int count)
+{
+    int i, cursorPos;
+    selection *sel;
+    WindowInfo *w, *tagwin;
+    Widget text, textPane;
+    
+    if (TagStackIndex <= 0) {
+	XBell(TheDisplay, 0);
+	return;
+    }
+    
+    TagStackIndex = TagStackIndex > count? TagStackIndex - count : 0;
+    
+    tagwin = TagStack[TagStackIndex].window;
+    cursorPos = TagStack[TagStackIndex].cursorPos;
+    sel = &TagStack[TagStackIndex].sel;
+
+    /* make sure caller window is still valid */
+    for (w=WindowList; w!=NULL; w=w->next) {
+    	if (w == tagwin) break;
+    }
+
+    if (w == NULL) {
+	XBell(TheDisplay, 0);
+	return;
+    }
+
+    /* if the split-pane has been remove, use the active pane */
+    textPane = tagwin->lastFocus;
+    for(i=0; i<=tagwin->nPanes; i++) {
+    	text = i==0 ? tagwin->textArea : tagwin->textPanes[i-1];
+    	if (text == TagStack[TagStackIndex].textPane) {
+	    textPane = text;
+	    break;
+	}
+    }
+
+    /* bring up the caller window & line */
+    RaiseDocumentWindow(tagwin);
+    if (sel->selected) {
+    	if (sel->rectangular)
+    	    BufRectSelect(tagwin->buffer, sel->start, sel->end,
+		    sel->rectStart, sel->rectEnd);
+    	else
+    	    BufSelect(tagwin->buffer, sel->start, sel->end);
+    } else
+    	BufUnselect(window->buffer);
+
+    /* see GotoMark() for the following code */
+    XtVaSetValues(textPane, textNautoShowInsertPos, False, NULL);
+    TextSetCursorPos(textPane, cursorPos);
+    MakeSelectionVisible(tagwin, textPane);
+    XtVaSetValues(textPane, textNautoShowInsertPos, True, NULL);
+
+    /* set keyboard focus to caller's pane */
+    tagwin->lastFocus = textPane;
+    XmProcessTraversal(tagwin->lastFocus, XmTRAVERSE_CURRENT);
+}
+
+/*
+** Keep the tagstack up to date across changes to the respective 
+** underlying buffer.
+*/
+void UpdateTagStack(WindowInfo *window, int pos, int nInserted,
+    	int nDeleted)
+{
+    int i;
+    
+    for (i=0; i<TagStackIndex; i++) {
+        if (TagStack[i].window == window) {
+    	    maintainSelection(&TagStack[i].sel, pos, nInserted,
+    	    	    nDeleted);
+    	    maintainPosition(&TagStack[i].cursorPos, pos, nInserted,
+    	    	    nDeleted);
+	}
+    }
 }
Index: source/tags.h
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/tags.h,v
retrieving revision 1.14
diff -u -r1.14 tags.h
--- source/tags.h	21 Jul 2004 11:32:05 -0000	1.14
+++ source/tags.h	26 Oct 2004 00:53:16 -0000
@@ -42,7 +42,17 @@
     short index;
 } tagFile;
 
+typedef struct _tagStackData {
+    WindowInfo *window;
+    Widget textPane;
+    int cursorPos;
+    selection sel;
+} tagStackData;
+
+#define TAG_STACK_DEPTH 20            /* size of tagstack */
+
 extern tagFile *TagsFileList;         /* list of loaded tags files */
+extern tagStackData TagStack[TAG_STACK_DEPTH]; /* stack of tag callers */
 extern tagFile *TipsFileList;         /* list of loaded calltips tag files */
 
 /* file_type and search_type arguments are to select between tips and tags,
@@ -60,6 +70,11 @@
 
 /* Routines for handling tags or tips from the current selection */
 void FindDefinition(WindowInfo *window, Time time, const char *arg);
+void PopTagStack(WindowInfo *window, int count);
+void ClearTagStack(void);
+void RemoveTagStack(WindowInfo *window);
+void UpdateTagStack(WindowInfo *window, int pos, int nInserted,
+    	int nDeleted);
 void FindDefCalltip(WindowInfo *window, Time time, const char *arg);
 
 /* Display (possibly finding first) a calltip.  Search type can only be 
Index: source/window.c
===================================================================
RCS file: /user/r28629/NEdit/cvsroot/nedit/source/window.c,v
retrieving revision 1.174
diff -u -r1.174 window.c
--- source/window.c	15 Sep 2004 22:50:58 -0000	1.174
+++ source/window.c	26 Oct 2004 00:53:16 -0000
@@ -45,6 +45,7 @@
 #include "preferences.h"
 #include "selection.h"
 #include "server.h"
+#include "tags.h"
 #include "shell.h"
 #include "macro.h"
 #include "highlight.h"
@@ -947,6 +948,9 @@
        there can be more than one dialog. */
     RemoveFromMultiReplaceDialog(window);
     
+    /* remove tag stack entries for this window */
+    RemoveTagStack(window);
+
     /* Destroy the file closed property for this file */
     DeleteFileClosedProperty(window);
 
@@ -2280,6 +2284,7 @@
     if (!window->ignoreModify) {
         UpdateMarkTable(window, pos, nInserted, nDeleted);
     }
+    UpdateTagStack(window, pos, nInserted, nDeleted);
     
     /* Check and dim/undim selection related menu items */
     if ((window->wasSelected && !selected) ||
