emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/wisi 5becb56 29/35: Release ada-mode 7.0.1, wisi 3.0.1


From: Stefan Monnier
Subject: [elpa] externals/wisi 5becb56 29/35: Release ada-mode 7.0.1, wisi 3.0.1
Date: Sat, 28 Nov 2020 14:47:57 -0500 (EST)

branch: externals/wisi
commit 5becb56e9e3c40eb1ed9d94780a1984357cf6651
Author: Stephen Leake <stephen_leake@stephe-leake.org>
Commit: Stephen Leake <stephen_leake@stephe-leake.org>

    Release ada-mode 7.0.1, wisi 3.0.1
---
 NEWS                                            |   28 +-
 README                                          |    9 +-
 build-wisitoken-bnf-generate.sh                 |    2 +-
 emacs_wisi_common_parse.adb                     |    8 +-
 emacs_wisi_common_parse.ads                     |    5 +-
 recover_stats.adb                               |  277 +++++
 run_wisi_common_parse.adb                       |    8 +-
 wisi-fringe.el                                  |  302 ++---
 wisi-parse-common.el                            |    7 +-
 wisi-prj.el                                     | 1466 +++++++++++++++++++++++
 wisi-process-parse.el                           |   18 +-
 wisi-run-indent-test.el                         |   67 +-
 wisi-skel.el                                    |  154 +++
 wisi.adb                                        |   94 +-
 wisi.ads                                        |   65 +-
 wisi.el                                         |  456 ++++---
 wisi.gpr => wisi.gpr.gp                         |    7 +-
 wisitoken-bnf-generate.adb                      |    2 +-
 wisitoken-bnf-output_ada_common.adb             |   32 +-
 wisitoken-parse-lr-mckenzie_recover-explore.adb |   12 +-
 wisitoken-parse-lr-mckenzie_recover.adb         |   43 +-
 wisitoken-parse-lr-mckenzie_recover.ads         |   15 +-
 wisitoken-parse-lr-parser.adb                   |   21 +-
 wisitoken-parse-lr-parser.ads                   |    2 +
 wisitoken-parse-lr.ads                          |    3 +-
 wisitoken-parse.adb                             |    9 +-
 wisitoken-parse.ads                             |   17 +-
 wisitoken-semantic_checks.ads                   |    3 +-
 wisitoken_grammar_re2c.c                        |  186 +--
 29 files changed, 2770 insertions(+), 548 deletions(-)

diff --git a/NEWS b/NEWS
index 8986e40..277c1ea 100644
--- a/NEWS
+++ b/NEWS
@@ -1,14 +1,38 @@
 GNU Emacs wisi NEWS -- history of user-visible changes.
 
-Copyright (C) 2019 Free Software Foundation, Inc.
+Copyright (C) 2014 - 2020 Free Software Foundation, Inc.
 
 Please send wisi bug reports to bug-gnu-emacs@gnu.org, with
 'wisi' in the subject. If possible, use M-x report-emacs-bug.
 
 
+* wisi 3.0.1
+30 Jan 2020
+
+** fix packaging bugs
+
+** improve xref integration
+
+* wisi 3.0.0
+19 Dec 2019
+
+** parser process protocol version 5
+
+** Add project.el integration. See wisi.info for more information.
+
+** Move autocase functions from ada-mode to wisi.
+
+** Replace most ada-mode function variables with dispatching on wisi-prj.
+
+** New commands `wisi-goto-containing-statement-start', 
`wisi-indent-containing-statement'.
+
+** Displayed parser errors are sorted in increasing error position.
+
 * wisi 2.2.1
 17 Aug 2019
 
+** parser process protocol version 4
+
 ** fix packaging bugs
 
 * wisi 2.2.0
@@ -49,8 +73,6 @@ Please send wisi bug reports to bug-gnu-emacs@gnu.org, with
    first one found is included in the current right hand side motion
    token chain.
 
-** The elisp parser and lexer are deleted.
-
 ** The process parser supports a new parse command `wisi-refactor',
    which returns a new message "Edit". It is intended for performing
    syntax-guided refactoring of code statements.
diff --git a/README b/README
index f23d1d8..f2c5329 100644
--- a/README
+++ b/README
@@ -1,8 +1,9 @@
-Emacs wisi package 2.2.1
+Emacs wisi package 3.0.1
 
-The wisi package provides utilities for using generalized LALR parsers
-(in elisp or external processes) to do indentation, fontification, and
-navigation. See ada-mode for an example of its use.
+The wisi package provides utilities for using generalized
+error-correcting LR parsers (in external processes) to do indentation,
+fontification, and navigation; and integration with Emacs package.el.
+See ada-mode for an example of its use.
 
 It also provides wisitoken-parse_table-mode, for navigating the
 diagnostic parse tables output by wisitoken-bnf-generate.
diff --git a/build-wisitoken-bnf-generate.sh b/build-wisitoken-bnf-generate.sh
index 0732e5a..faa4094 100755
--- a/build-wisitoken-bnf-generate.sh
+++ b/build-wisitoken-bnf-generate.sh
@@ -4,4 +4,4 @@
 # wisitoken development tree; see
 # http://stephe-leake.org/ada/wisitoken.html
 
-gprbuild -p -P wisi.gpr wisitoken-bnf-generate
+gprbuild -p -j8 -P wisi.gpr wisitoken-bnf-generate
diff --git a/emacs_wisi_common_parse.adb b/emacs_wisi_common_parse.adb
index f74ce37..183aaf7 100644
--- a/emacs_wisi_common_parse.adb
+++ b/emacs_wisi_common_parse.adb
@@ -210,6 +210,7 @@ package body Emacs_Wisi_Common_Parse is
          Result.Parse_Begin_Char_Pos := WisiToken.Buffer_Pos (Get_Integer 
(Command_Line, Last));
          Result.Parse_Begin_Line     := WisiToken.Line_Number_Type 
(Get_Integer (Command_Line, Last));
          Result.Parse_End_Line       := WisiToken.Line_Number_Type 
(Get_Integer (Command_Line, Last));
+         Result.Parse_Begin_Indent   := Get_Integer (Command_Line, Last);
          Result.Debug_Mode           := 1 = Get_Integer (Command_Line, Last);
          Result.Parse_Verbosity      := Get_Integer (Command_Line, Last);
          Result.Action_Verbosity     := Get_Integer (Command_Line, Last);
@@ -309,7 +310,8 @@ package body Emacs_Wisi_Common_Parse is
                   Trace_Action   := Params.Action_Verbosity;
                   Debug_Mode     := Params.Debug_Mode;
 
-                  Partial_Parse_Active := Params.Partial_Parse_Active;
+                  Partial_Parse_Active        := Params.Partial_Parse_Active;
+                  Parser.Partial_Parse_Active := Params.Partial_Parse_Active;
 
                   if WisiToken.Parse.LR.McKenzie_Defaulted (Parser.Table.all) 
then
                      --  There is no McKenzie information; don't override that.
@@ -351,6 +353,8 @@ package body Emacs_Wisi_Common_Parse is
 
                   Parser.Lexer.Reset_With_String_Access
                     (Buffer, Params.Source_File_Name, Params.Begin_Char_Pos, 
Params.Begin_Line);
+
+                  --  Parser.Line_Begin_Token First, Last set by Lex_All
                   begin
                      Parser.Parse;
                   exception
@@ -413,7 +417,7 @@ package body Emacs_Wisi_Common_Parse is
                      Base_Terminals    => Parser.Terminals'Unrestricted_Access,
                      Begin_Line        => Params.Parse_Begin_Line,
                      End_Line          => Params.Parse_End_Line,
-                     Begin_Indent      => 0,
+                     Begin_Indent      => Params.Parse_Begin_Indent,
                      Params            => "");
 
                   if Params.Max_Parallel > 0 then
diff --git a/emacs_wisi_common_parse.ads b/emacs_wisi_common_parse.ads
index 1d75de6..e646eba 100644
--- a/emacs_wisi_common_parse.ads
+++ b/emacs_wisi_common_parse.ads
@@ -24,7 +24,7 @@ with Wisi;
 with WisiToken.Parse.LR.Parser;
 package Emacs_Wisi_Common_Parse is
 
-   Protocol_Version : constant String := "4";
+   Protocol_Version : constant String := "5";
    --  Protocol_Version defines the data sent between elisp and the
    --  background process, except for the language-specific parameters,
    --  which are defined by the Language_Protocol_Version parameter to
@@ -141,6 +141,9 @@ package Emacs_Wisi_Common_Parse is
       Parse_End_Line   : WisiToken.Line_Number_Type;
       --  Line numbers of lines containing Parse_Begin_Byte_Pos, 
Parse_End_Byte_Pos
 
+      Parse_Begin_Indent : Integer;
+      --  Indentation of Parse_Begin_Line
+
       Debug_Mode       : Boolean;
       Parse_Verbosity  : Integer;
       Action_Verbosity : Integer;
diff --git a/recover_stats.adb b/recover_stats.adb
new file mode 100644
index 0000000..9172cb8
--- /dev/null
+++ b/recover_stats.adb
@@ -0,0 +1,277 @@
+--  Abstract :
+--
+--  Summarize error recover log.
+--
+--  Copyright (C) 2019 Stephen Leake All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, or (at
+--  your option) any later version. This program is distributed in the
+--  hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+--  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+--  PURPOSE. See the GNU General Public License for more details. You
+--  should have received a copy of the GNU General Public License
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Exceptions;
+with Ada.Long_Float_Text_IO;
+with Ada.Strings.Fixed; use Ada.Strings.Fixed;
+with Ada.Strings.Maps;
+with Ada.Text_IO; use Ada.Text_IO;
+with GNAT.Traceback.Symbolic;
+with SAL.Gen_Stats.Gen_Image;
+with SAL.Long_Float_Stats;
+with WisiToken.Parse.LR;
+procedure Recover_Stats
+is
+   subtype Strategies is WisiToken.Parse.LR.Strategies;
+
+   File : File_Type;
+
+   Delimiters : constant Ada.Strings.Maps.Character_Set := 
Ada.Strings.Maps.To_Set (",() ");
+   Number     : constant Ada.Strings.Maps.Character_Set := 
Ada.Strings.Maps.To_Set ("0123456789");
+
+   type Strategy_Counts is array (Strategies) of Natural;
+
+   type Recover_Label is (Full, Partial);
+
+   type Recover_Summary is record
+      Event_Count : Integer := 0;
+      --  1 per recover event (1 line in log file)
+
+      Enqueue_Stats : SAL.Long_Float_Stats.Stats_Type;
+      Check_Stats   : SAL.Long_Float_Stats.Stats_Type;
+
+      Strat_Counts_Total   : Strategy_Counts := (others => 0);
+      Strat_Counts_Present : Strategy_Counts := (others => 0);
+      --  1 per recover event if used
+
+      Ignore_Error : Integer := 0;
+      --  ie, error is name mismatch.
+
+      Recover_Count_Present : Integer := 0;
+      --  1 per parser in recover result
+
+      Recover_Count_Total : Integer := 0;
+      --  Sum of all strategy counts
+
+      Fail_Event_Count      : Integer := 0; -- for all reasons
+      Fail_Enqueue_Limit    : Integer := 0;
+      Fail_No_Configs_Left  : Integer := 0;
+      Fail_Programmer_Error : Integer := 0;
+      Fail_Other            : Integer := 0;
+   end record;
+
+   Summary : array (Recover_Label) of Recover_Summary;
+begin
+   Open (File, In_File, Ada.Command_Line.Argument (1));
+
+   loop
+      exit when End_Of_File (File);
+      declare
+         --  The recover log is written by code in
+         --  wisitoken-parse-lr-parser.adb Parse (search for Recover_Log).
+         --
+         --  A line has the syntax:
+         --  yyyy-mm-dd hh:mm:ss <partial> <success> pre_parser_count 
'<file_name>' (<parser_data>)...
+         --
+         --  where there is one (<parser_data) for each parser active after 
recover. <parser_data> is:
+         --
+         --  (<strategy_counts>) <enqueue_count> <check_count> <success>
+         --
+         --  Note that the per-parser success is always TRUE; it would not be
+         --  active if recover had failed.
+
+         Line  : constant String := Get_Line (File);
+         First : Integer         := Index (Line, " "); -- after date
+         Last  : Integer;
+
+         Label : Recover_Label := Full;
+
+         function Line_Eq (Item : in String) return Boolean
+         is begin
+            return Line (First .. First + Item'Length - 1) = Item;
+         end Line_Eq;
+
+         function Next_Integer return Integer
+         is begin
+            Find_Token
+              (Line, Number,
+               From  => Last + 1,
+               Test  => Ada.Strings.Inside,
+               First => First,
+               Last  => Last);
+            return Integer'Value (Line (First .. Last));
+         exception
+         when Constraint_Error =>
+            raise Constraint_Error with "bad integer '" & Line (First .. Last 
- 1) & "' " &
+              Ada.Text_IO.Count'Image (Ada.Text_IO.Line (File) - 1) & 
First'Image & Last'Image;
+         end Next_Integer;
+
+         function Next_Boolean return  Boolean
+         is begin
+            First := Last + 2;
+            Last  := -1 + Index (Line, Delimiters, First);
+            return Boolean'Value (Line (First .. Last));
+         end Next_Boolean;
+
+         function Read_Strat_Counts (Strategy_Found : out Boolean) return 
Strategy_Counts
+         is begin
+            Strategy_Found := False;
+            Last := Index (Line, "(", Last + 1);
+            return Result : Strategy_Counts do
+               for I in Strategies loop
+                  Result (I) := Next_Integer;
+                  if Result (I) > 0 then
+                     Strategy_Found := True;
+                  end if;
+               end loop;
+               Last := 1 + Index (Line, ")", Last + 1);
+            end return;
+         end Read_Strat_Counts;
+
+      begin
+         First := Index (Line, " ", First + 1); -- after time
+         Last  := Index (Line, " ", First + 1); -- after Partial_Parse_Active
+         if Boolean'Value (Line (First + 1 .. Last - 1)) then
+            Label := Partial;
+         end if;
+
+         Summary (Label).Event_Count := Summary (Label).Event_Count + 1;
+
+         First := Last + 1;
+         if Line (First .. First + 3) = "FAIL" then
+            Summary (Label).Fail_Event_Count := Summary 
(Label).Fail_Event_Count + 1;
+            First := First + 4;
+
+            if Line_Eq ("NO_CONFIGS_LEFT") then
+               Summary (Label).Fail_No_Configs_Left := Summary 
(Label).Fail_No_Configs_Left + 1;
+            elsif Line_Eq ("ENQUEUE_LIMIT") then
+               Summary (Label).Fail_Enqueue_Limit := Summary 
(Label).Fail_Enqueue_Limit + 1;
+            elsif Line_Eq ("PROGRAMMER_ERROR") then
+               Summary (Label).Fail_Programmer_Error := Summary 
(Label).Fail_Programmer_Error + 1;
+            else
+               Summary (Label).Fail_Other := Summary (Label).Fail_Other + 1;
+            end if;
+
+         else
+            --  Process per-parser data
+            Last := Index (Line, "(", Last + 1);
+            loop
+               exit when Line (Last + 1) = ')';
+               declare
+                  Strategy_Found : Boolean;
+                  Strat_Counts   : constant Strategy_Counts := 
Read_Strat_Counts (Strategy_Found);
+                  Enqueue_Count  : constant Integer         := Next_Integer;
+                  Check_Count    : constant Integer         := Next_Integer;
+                  Success        : constant Boolean         := Next_Boolean;
+                  pragma Unreferenced (Success);
+               begin
+                  Summary (Label).Recover_Count_Present := Summary 
(Label).Recover_Count_Present + 1;
+
+                  if not Strategy_Found then
+                     Summary (Label).Ignore_Error := Summary 
(Label).Ignore_Error + 1;
+                  else
+                     --  We don't include Ignore_Error enqueue and check 
counts in the
+                     --  stats, because they distort them towards 1.
+                     Summary (Label).Enqueue_Stats.Accumulate (Long_Float 
(Enqueue_Count));
+                     Summary (Label).Check_Stats.Accumulate (Long_Float 
(Check_Count));
+                     for I in Strategies loop
+                        Summary (Label).Recover_Count_Total    :=
+                          Summary (Label).Recover_Count_Total + Strat_Counts 
(I);
+                        Summary (Label).Strat_Counts_Total (I) :=
+                          Summary (Label).Strat_Counts_Total (I) + 
Strat_Counts (I);
+                        if Strat_Counts (I) > 0 then
+                           Summary (Label).Strat_Counts_Present (I) := Summary 
(Label).Strat_Counts_Present (I) + 1;
+                        end if;
+                     end loop;
+                  end if;
+               end;
+            end loop;
+         end if;
+      end;
+   end loop;
+
+   declare
+      use Ada.Strings;
+
+      Label_Field     : String (1 .. 23); -- fits strategy and fail labels
+      Count_Field     : String (1 .. 8);
+      Percent_Field   : String (1 .. 4);
+      --  Shared by Put_If, Put_Percent
+
+      procedure Put_If
+        (Summary_Label : in Recover_Label;
+         Name          : in String;
+         Count         : in Integer;
+         Always        : in Boolean := False)
+      is
+         Percent_Present : constant Integer :=
+           Integer (100.0 * Float (Count) / Float (Summary 
(Summary_Label).Event_Count));
+      begin
+         if Count > 0 or Always then
+            Move (Name, Label_Field); Put (Label_Field & " => ");
+            Move (Count'Image, Count_Field, Justify => Right); Put 
(Count_Field);
+            Move (Percent_Present'Image & "%", Percent_Field, Justify => 
Right); Put_Line (Percent_Field);
+         end if;
+      end Put_If;
+
+      package Stats_Image is new SAL.Long_Float_Stats.Gen_Image
+        (Real_IO           => Ada.Long_Float_Text_IO,
+         Default_Mean_Fore => 7,
+         Default_Mean_Aft  => 0,
+         Default_Mean_Exp  => 0,
+         Default_Sd_Fore   => 7,
+         Default_Sd_Aft    => 1,
+         Default_Sd_Exp    => 0);
+
+      procedure Put_Percent (Summary_Label : in Recover_Label; Present, Total 
: in Integer; Name : in String)
+      is
+         Percent_Present : constant Integer :=
+           Integer (100.0 * Float (Present) / Float (Summary 
(Summary_Label).Recover_Count_Present));
+         Percent_Total   : constant Integer :=
+           Integer (100.0 * Float (Total) / Float (Summary 
(Summary_Label).Recover_Count_Total));
+      begin
+         Move (Name, Label_Field); Put (Label_Field);
+         Move (Present'Image, Count_Field, Justify => Right); Put 
(Count_Field);
+         Move (Percent_Present'Image & "%", Percent_Field, Justify => Right); 
Put (Percent_Field & " /");
+         Move (Total'Image, Count_Field, Justify => Right); Put (Count_Field);
+         Move (Percent_Total'Image & "%", Percent_Field, Justify => Right); 
Put_Line (Percent_Field);
+      end Put_Percent;
+
+   begin
+      for I in Recover_Label loop
+         Put_Line (I'Image);
+         Put_Line ("present/total:" & Summary (I).Event_Count'Image & " /" & 
Summary (I).Recover_Count_Total'Image);
+         if Summary (I).Event_Count > 0 then
+            Put_Line ("           mean        std. dev.    min     max");
+            Put_Line ("Enqueue: " & Stats_Image.Image (Summary 
(I).Enqueue_Stats.Display));
+            Put_Line ("Check:   " & Stats_Image.Image (Summary 
(I).Check_Stats.Display));
+            Put_If (I, "FAIL", Summary (I).Fail_Event_Count, Always => True);
+            Put_If (I, "FAIL_ENQUEUE_LIMIT", Summary (I).Fail_Enqueue_Limit);
+            Put_If (I, "FAIL_NO_CONFIGS_LEFT", Summary 
(I).Fail_No_Configs_Left);
+            Put_If (I, "FAIL_PROGRAMMER_ERROR", Summary 
(I).Fail_Programmer_Error);
+            Put_If (I, "FAIL_OTHER", Summary (I).Fail_Other);
+            for J in Strategies loop
+               Put_Percent
+                 (I,
+                  Summary (I).Strat_Counts_Present (J),
+                  Summary (I).Strat_Counts_Total (J),
+                  J'Image);
+            end loop;
+            Put_Percent (I, Summary (I).Ignore_Error, Summary 
(I).Ignore_Error, "Ignore_Error");
+         end if;
+         New_Line;
+      end loop;
+   end;
+exception
+when E : others =>
+   Put_Line (Ada.Exceptions.Exception_Name (E) & ": " & 
Ada.Exceptions.Exception_Message (E));
+   Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E));
+end Recover_Stats;
diff --git a/run_wisi_common_parse.adb b/run_wisi_common_parse.adb
index e4a45c0..517b631 100644
--- a/run_wisi_common_parse.adb
+++ b/run_wisi_common_parse.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--  Copyright (C) 2018 - 2020 Free Software Foundation, Inc.
 --
 --  This program is free software; you can redistribute it and/or
 --  modify it under terms of the GNU General Public License as
@@ -45,7 +45,7 @@ package body Run_Wisi_Common_Parse is
       Put_Line ("   1 - shows spawn/terminate parallel parsers, error recovery 
enter/exit");
       Put_Line ("   2 - add each parser cycle, error recovery enqueue/check");
       Put_Line ("   3 - parse stack in each cycle, error recovery parse 
actions");
-      Put_Line ("   4 - add lexer debug");
+      Put_Line ("   4 - add lexer debug, dump syntax tree");
       Put_Line ("--check_limit n  : set error recover token check limit" &
                   (if Parser.Table = null then ""
                    else "; default" & 
Parser.Table.McKenzie_Param.Check_Limit'Image));
@@ -218,10 +218,12 @@ package body Run_Wisi_Common_Parse is
             return;
          end;
 
+         --  Parser.Line_Begin_Token First, Last set by Lex_All
+
          if Cl_Params.Command = Refactor or else Cl_Params.End_Line = 
Invalid_Line_Number then
             --  User did not provide; run lexer to get end line.
             declare
-               Token : Base_Token;
+               Token       : Base_Token;
                Lexer_Error : Boolean;
                pragma Unreferenced (Lexer_Error);
             begin
diff --git a/wisi-fringe.el b/wisi-fringe.el
index 82f4c6a..2194b09 100644
--- a/wisi-fringe.el
+++ b/wisi-fringe.el
@@ -1,150 +1,152 @@
-;;; wisi-fringe.el --- show approximate error locations in the fringe
-;;
-;; Copyright (C) 2018 - 2019  Free Software Foundation, Inc.
-;;
-;; This file is part of GNU Emacs.
-;;
-;; GNU Emacs is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-;;
-;; GNU Emacs is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;; GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-;;
-;; Design:
-;;
-;; Bitmaps are displayed in the fringe by putting a 'display property
-;; on buffer text. However, just doing that also hides the buffer
-;; text. To avoid that, we put the ’display property on a string, and
-;; then an overlay containing that string as ’before-string or
-;; ’after-string on the newline of a buffer line.
-;;
-;; We show approximate error positions in the entire buffer with
-;; single-pixel lines in the right fringe, and mark error lines with
-;; ’!!’ in the left fringe.
-
-(defun wisi-fringe-create-bitmaps ()
-  "Return an array of bitmap symbols containing the fringe bitmaps."
-  ;; First create the ’!!’ bitmap.
-  (define-fringe-bitmap 'wisi-fringe--double-exclaim-bmp
-    (vector
-     #b00000000
-     #b01100110
-     #b01100110
-     #b01100110
-     #b01100110
-     #b01100110
-     #b00000000
-     #b01100110
-     #b01010110
-     #b00000000))
-
-  ;; In condensing the entire buffer to the current window height, we
-  ;; assume a 10 point font, which allows 6 distinct line positions
-  ;; each one pixel high, with one blank pixel between.
-
-  (let ((result (make-vector 64 nil))
-       (i 1))
-    (while (<= i (length result))
-      (aset result (1- i)
-           (define-fringe-bitmap (intern (format "wisi-fringe--line-%d-bmp" i))
-             (vector
-              (if (>= i 32) #b11111111 #b00000000)
-              #b00000000
-              (if (>= (% i 32) 16) #b11111111 #b00000000)
-              #b00000000
-              (if (>= (% i 16) 8) #b11111111 #b00000000)
-              #b00000000
-              (if (>= (% i 8) 4) #b11111111 #b00000000)
-              #b00000000
-              (if (>= (% i 4) 2) #b11111111 #b00000000)
-              #b00000000
-              (if (>= (% i 2) 1) #b11111111 #b00000000)
-              )))
-      (setq i (1+ i)))
-    result))
-
-(defconst wisi-fringe-bitmaps (wisi-fringe-create-bitmaps)
-  "Array of 64 bitmap symbols.")
-
-(defun wisi-fringe--put-right (line bitmap-index)
-  (save-excursion
-    (goto-char (point-min))
-    (forward-line (1- line))
-    (let* ((endpos (line-end-position))
-          (ov (make-overlay endpos (1+ endpos)))
-          (bmp (aref wisi-fringe-bitmaps bitmap-index)))
-      (overlay-put ov 'after-string (propertize "-" 'display (list 
'right-fringe bmp 'compilation-error)))
-      (overlay-put ov 'wisi-fringe t)
-      )))
-
-(defun wisi-fringe--put-left (line)
-  (save-excursion
-    (goto-char (point-min))
-    (forward-line (1- line))
-    (let* ((endpos (line-end-position))
-          (ov (make-overlay endpos (1+ endpos)))
-          (bmp 'wisi-fringe--double-exclaim-bmp))
-      (overlay-put ov 'before-string (propertize "-" 'display (list 
'left-fringe bmp 'compilation-error)))
-      (overlay-put ov 'wisi-fringe t)
-      )))
-
-(defun wisi-fringe--scale (error-line buffer-lines window-line-first 
window-lines)
-  "Return a cons (LINE . BIN) for ERROR-LINE,
-where LINE is the line to display the error bar on, and BIN is a
-6-bit bit vector giving the relative position in that line.
-BUFFER-LINES is the count of lines in the buffer.
-WINDOW-LINE-FIRST is the first and last lines of the buffer
-visible in the window. WINDOW-LINES is the count of lines visible
-in the window."
-  ;; If the end of buffer is inside the window, and this calculation
-  ;; puts a mark after that, it will actually be put on the last real
-  ;; line. That’s good enough for our purposes.
-
-  ;; partial-lines / window-line = 6
-  ;; buffer-lines / window-line = 1/scale
-  ;; buffer-lines / partial-line  = (window-line / partial-lines) * 
(buffer-lines / window-line) = 1/6 * 1/scale
-  (let* ((scale (/ window-lines (float buffer-lines)))
-        (line (floor (* scale error-line)))
-        (rem (- error-line (floor (/ line scale)))))
-    (cons (+ window-line-first line) (lsh 1 (min 5 (floor (* rem (* 6 
scale))))))))
-
-(defun wisi-fringe-clean ()
-  "Remove all wisi-fringe marks."
-  (remove-overlays (point-min) (point-max) 'wisi-fringe t))
-
-(defun wisi-fringe-display-errors (positions)
-  "Display markers in the left and right fringe for each buffer position in 
POSITIONS.
-The buffer containing POSITIONS must be current, and the window
-displaying that buffer must be current."
-  ;; FIXME: recompute fringe display on scroll!
-  (wisi-fringe-clean)
-  (let (scaled-posns
-       (buffer-lines (line-number-at-pos (point-max)))
-       (window-lines (window-height))
-       (window-pos-first (window-start))
-       (window-pos-last  (window-end))
-       (window-line-first (line-number-at-pos (window-start))))
-    (dolist (pos positions)
-      (let* ((line (line-number-at-pos pos))
-            (scaled-pos (wisi-fringe--scale line buffer-lines 
window-line-first window-lines)))
-       (when (and (>= pos window-pos-first)
-                  (<= pos window-pos-last))
-         (wisi-fringe--put-left line))
-       (if (and scaled-posns
-                (= (caar scaled-posns) (car scaled-pos)))
-           (setcdr (car scaled-posns) (logior (cdar scaled-posns) (cdr 
scaled-pos)))
-         (push scaled-pos scaled-posns))
-       ))
-
-    (dolist (pos scaled-posns)
-      (wisi-fringe--put-right (car pos) (1- (cdr pos))))
-    ))
-
-(provide 'wisi-fringe)
+;;; wisi-fringe.el --- show approximate error locations in the fringe
+;;
+;; Copyright (C) 2018 - 2019  Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+;;
+;; Design:
+;;
+;; Bitmaps are displayed in the fringe by putting a 'display property
+;; on buffer text. However, just doing that also hides the buffer
+;; text. To avoid that, we put the ’display property on a string, and
+;; then an overlay containing that string as ’before-string or
+;; ’after-string on the newline of a buffer line.
+;;
+;; We show approximate error positions in the entire buffer with
+;; single-pixel lines in the right fringe, and mark error lines with
+;; ’!!’ in the left fringe.
+
+(defun wisi-fringe-create-bitmaps ()
+  "Return an array of bitmap symbols containing the fringe bitmaps."
+  ;; First create the ’!!’ bitmap.
+  (define-fringe-bitmap 'wisi-fringe--double-exclaim-bmp
+    (vector
+     #b00000000
+     #b01100110
+     #b01100110
+     #b01100110
+     #b01100110
+     #b01100110
+     #b00000000
+     #b01100110
+     #b01010110
+     #b00000000))
+
+  ;; In condensing the entire buffer to the current window height, we
+  ;; assume a 10 point font, which allows 6 distinct line positions
+  ;; each one pixel high, with one blank pixel between.
+
+  (let ((result (make-vector 64 nil))
+       (i 1))
+    (while (<= i (length result))
+      (aset result (1- i)
+           (define-fringe-bitmap (intern (format "wisi-fringe--line-%d-bmp" i))
+             (vector
+              (if (>= i 32) #b11111111 #b00000000)
+              #b00000000
+              (if (>= (% i 32) 16) #b11111111 #b00000000)
+              #b00000000
+              (if (>= (% i 16) 8) #b11111111 #b00000000)
+              #b00000000
+              (if (>= (% i 8) 4) #b11111111 #b00000000)
+              #b00000000
+              (if (>= (% i 4) 2) #b11111111 #b00000000)
+              #b00000000
+              (if (>= (% i 2) 1) #b11111111 #b00000000)
+              )))
+      (setq i (1+ i)))
+    result))
+
+(defconst wisi-fringe-bitmaps (wisi-fringe-create-bitmaps)
+  "Array of 64 bitmap symbols.")
+
+(defun wisi-fringe--put-right (line bitmap-index)
+  (save-excursion
+    (goto-char (point-min))
+    (forward-line (1- line))
+    (let* ((endpos (line-end-position))
+          (ov (make-overlay endpos (1+ endpos)))
+          (bmp (aref wisi-fringe-bitmaps bitmap-index)))
+      (overlay-put ov 'after-string (propertize "-" 'display (list 
'right-fringe bmp 'compilation-error)))
+      (overlay-put ov 'wisi-fringe t)
+      )))
+
+(defun wisi-fringe--put-left (line)
+  (save-excursion
+    (goto-char (point-min))
+    (forward-line (1- line))
+    (let* ((endpos (line-end-position))
+          (ov (make-overlay endpos (1+ endpos)))
+          (bmp 'wisi-fringe--double-exclaim-bmp))
+      (overlay-put ov 'before-string (propertize "-" 'display (list 
'left-fringe bmp 'compilation-error)))
+      (overlay-put ov 'wisi-fringe t)
+      )))
+
+(defun wisi-fringe--scale (error-line buffer-lines window-line-first 
window-lines)
+  "Return a cons (LINE . BIN) for ERROR-LINE,
+where LINE is the line to display the error bar on, and BIN is a
+6-bit bit vector giving the relative position in that line.
+BUFFER-LINES is the count of lines in the buffer.
+WINDOW-LINE-FIRST is the first and last lines of the buffer
+visible in the window. WINDOW-LINES is the count of lines visible
+in the window."
+  ;; If the end of buffer is inside the window, and this calculation
+  ;; puts a mark after that, it will actually be put on the last real
+  ;; line. That’s good enough for our purposes.
+
+  ;; partial-lines / window-line = 6
+  ;; buffer-lines / window-line = 1/scale
+  ;; buffer-lines / partial-line  = (window-line / partial-lines) * 
(buffer-lines / window-line) = 1/6 * 1/scale
+  (let* ((scale (/ window-lines (float buffer-lines)))
+        (line (floor (* scale error-line)))
+        (rem (- error-line (floor (/ line scale)))))
+    (cons (+ window-line-first line) (lsh 1 (min 5 (floor (* rem (* 6 
scale))))))))
+
+(defun wisi-fringe-clean ()
+  "Remove all wisi-fringe marks."
+  (remove-overlays (point-min) (point-max) 'wisi-fringe t))
+
+(defun wisi-fringe-display-errors (positions)
+  "Display markers in the left and right fringe for each buffer position in 
POSITIONS.
+The buffer containing POSITIONS must be current, and the window
+displaying that buffer must be current."
+  ;; We don't recompute fringe display on scroll, because the user
+  ;; will probably have edited the code by then, triggering a new
+  ;; parse.
+  (wisi-fringe-clean)
+  (let (scaled-posns
+       (buffer-lines (line-number-at-pos (point-max)))
+       (window-lines (window-height))
+       (window-pos-first (window-start))
+       (window-pos-last  (window-end))
+       (window-line-first (line-number-at-pos (window-start))))
+    (dolist (pos positions)
+      (let* ((line (line-number-at-pos pos))
+            (scaled-pos (wisi-fringe--scale line buffer-lines 
window-line-first window-lines)))
+       (when (and (>= pos window-pos-first)
+                  (<= pos window-pos-last))
+         (wisi-fringe--put-left line))
+       (if (and scaled-posns
+                (= (caar scaled-posns) (car scaled-pos)))
+           (setcdr (car scaled-posns) (logior (cdar scaled-posns) (cdr 
scaled-pos)))
+         (push scaled-pos scaled-posns))
+       ))
+
+    (dolist (pos scaled-posns)
+      (wisi-fringe--put-right (car pos) (1- (cdr pos))))
+    ))
+
+(provide 'wisi-fringe)
diff --git a/wisi-parse-common.el b/wisi-parse-common.el
index dd9d862..2140b50 100644
--- a/wisi-parse-common.el
+++ b/wisi-parse-common.el
@@ -1,4 +1,4 @@
-;;; wisi-parse-common.el --- declarations used by wisi-parse.el, 
wisi-ada-parse.el, and wisi.el
+;;; wisi-parse-common.el --- declarations used by wisi-parse.el, 
wisi-ada-parse.el, and wisi.el -*- lexical-binding:t -*-
 ;;
 ;; Copyright (C) 2014, 2015, 2017 - 2019  Free Software Foundation, Inc.
 ;;
@@ -74,10 +74,9 @@ and parse the whole buffer."
 (cl-defgeneric wisi-parse-format-language-options ((parser wisi-parser))
   "Return a string to be sent to the parser, containing settings
 for the language-specific parser options."
-  ;; not needed for the elisp parser, which can see the options directly.
   )
 
-(cl-defgeneric wisi-parse-expand-region ((parser wisi-parser) begin end)
+(cl-defgeneric wisi-parse-expand-region ((_parser wisi-parser) begin end)
   "Return a cons SEND-BEGIN . SEND-END that is an expansion of
 region BEGIN END that starts and ends at points the parser can
 handle gracefully."
@@ -119,7 +118,7 @@ Return nil if no match found before eob."
     (goto-char (cdr region))
     ))
 
-(cl-defgeneric wisi-parse-adjust-indent ((parser wisi-parser) indent _repair)
+(cl-defgeneric wisi-parse-adjust-indent ((_parser wisi-parser) indent _repair)
   "Adjust INDENT for REPAIR (a wisi--parse-error-repair struct). Return new 
indent."
   indent)
 
diff --git a/wisi-prj.el b/wisi-prj.el
new file mode 100644
index 0000000..833a514
--- /dev/null
+++ b/wisi-prj.el
@@ -0,0 +1,1466 @@
+;;; wisi-prj.el --- project definition files -*- lexical-binding:t -*-
+;;
+;; Copyright (C) 2019 - 2020  Free Software Foundation, Inc.
+;;
+;; Author: Stephen Leake <stephen_leake@member.fsf.org>
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Usage:
+;;
+;; See wisi.info (compiled from wisi.texi).
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'find-file)
+(require 'wisi)
+
+(cl-defstruct wisi-prj
+  name     ;; A user-friendly string, used in menus and messages.
+
+  compile-env
+  ;; List of strings NAME=VALUE for `compilation-environment'; used
+  ;; when running the compiler or makefile. Also prepended to
+  ;; `process-environment' when the project file is parsed, or when
+  ;; the project file is used by a tool in an external process.
+
+  file-env
+  ;; Environment (list of strings NAME=VALUE) set in project file;
+  ;; prepended to `process-environment' running tools in an external
+  ;; process.
+
+  compiler
+  xref
+  ;; xref functionality is often provided by the compiler. We allow
+  ;; for separate compiler and xref objects, to handle the case where
+  ;; the compiler is a cross-compiler for an embedded target, and xref
+  ;; is provided by a host compiler.
+
+  (case-exception-files nil)
+  ;; List of casing exception files; from `casing' project variable.
+  ;;
+  ;; New exceptions may be added interactively via
+  ;; `wisi-case-create-exception'.  If an exception is defined in
+  ;; multiple files, the first occurence is used.
+  ;;
+  ;; The file format is one word per line, which gives the casing to be
+  ;; used for that word in source code.  If the line starts with
+  ;; the character *, then the exception will be used for partial
+  ;; words that either start at the beginning of a word or after a _
+  ;; character, and end either at the end of the word or at a _
+  ;; character.  Characters after the first word are ignored, and not
+  ;; preserved when the list is written back to the file."
+
+  (case-full-exceptions '())
+  ;; Alist of full words that have special casing, built from
+  ;; case-exception-files. Indexed by properly cased word; value is t.
+
+  (case-partial-exceptions '())
+  ;; Alist of partial words that have special casing, built from
+  ;; project casing files list partial word exceptions. Indexed by
+  ;; properly cased word; value is t.
+
+  source-path ;; list of absolute directory file names
+
+  file-pred
+  ;; Function taking an absolute file name, returns non-nil
+  ;; if the file should be included in `project-files'.
+  )
+
+(defun wisi-prj-require-prj ()
+  "Return current `wisi-prj' object.
+Throw an error if current project is not an wisi-prj."
+  (let ((prj (project-current)))
+    (if (wisi-prj-p prj)
+       prj
+      (error "current project is not a wisi project."))))
+
+(defun wisi-prj-current-prj ()
+  "Return current `wisi-prj' object.
+If (project-current) does not return a wisi-prj, return a default prj."
+  (let ((prj (project-current)))
+    (if (wisi-prj-p prj)
+       prj
+      (make-wisi-prj :name "default"))))
+
+(defvar wisi-prj-file-extensions (list "prj")
+  "List of wisi project file extensions.
+Used when searching for project files.")
+
+(defvar wisi-prj--cache nil
+  "Alist holding currently parsed project objects.
+Indexed by absolute project file name.")
+
+(cl-defgeneric wisi-prj-default (prj)
+  "Return a project with default values.
+Used to reset a project before refreshing it.")
+
+(cl-defgeneric wisi-prj-parse-one (_project _name _value)
+  "If recognized by PROJECT, set NAME, VALUE in PROJECT, return non-nil.
+Else return nil."
+  nil)
+
+(cl-defgeneric wisi-prj-parse-final (_project _prj-file-name)
+  "Do any final processing on PROJECT
+after the project file PRJ-FILE-NAME is parsed."
+  nil)
+
+(cl-defgeneric wisi-prj-select (project)
+  "PROJECT is selected; perform any required actions.")
+
+(cl-defgeneric wisi-prj-deselect (project)
+  "PROJECT is deselected; undo any select actions.")
+
+(cl-defgeneric wisi-prj-refresh-cache (prj not-full)
+  "Reparse the project file for PRJ, refresh all cached data in PRJ.
+If NOT-FULL is non-nil, very slow refresh operations may be skipped.")
+
+(cl-defgeneric wisi-prj-identifier-ignore-case (prj)
+  "Return non-nil if case should be ignored when comparing identifiers.")
+
+;; We provide nil defaults for some methods, because some language
+;; modes don't have a language-specific compiler (eg java-wisi) or
+;; xref process (eg gpr-mode).
+
+(cl-defgeneric wisi-compiler-parse-one (compiler project name value)
+  "Set NAME, VALUE in COMPILER, if recognized by COMPILER.
+PROJECT is an `wisi-prj' object; COMPILER is `wisi-prj-compiler'.")
+
+(cl-defgeneric wisi-compiler-parse-final (_compiler _project _prj-file-name)
+  "Do any compiler-specific processing on COMPILER and PROJECT
+after the project file PRJ-FILE-NAME is parsed."
+  nil)
+
+(cl-defgeneric wisi-compiler-select-prj (_compiler _project)
+  "PROJECT has been selected; do any compiler-specific actions required."
+  nil)
+
+(cl-defgeneric wisi-compiler-deselect-prj (_compiler _project)
+  "PROJECT has been de-selected; undo any compiler-specific select actions."
+  nil)
+
+(cl-defgeneric wisi-compiler-show-prj-path (compiler)
+  "Display buffer listing project file search path.")
+
+(cl-defgeneric wisi-compiler-fix-error (compiler source-buffer)
+  "Attempt to fix a compilation error, return non-nil if fixed.
+Current buffer is compilation buffer; point is at an error message.
+SOURCE-BUFFER contains the source code referenced in the error message.")
+
+(cl-defgeneric wisi-xref-parse-one (_xref _project _name _value)
+  "If recognized by XREF, set NAME, VALUE in XREF, return non-nil.
+Else return nil."
+  nil)
+
+(cl-defgeneric wisi-xref-parse-final (_xref _project _prj-file-name)
+  "Do any xref-specific processing on XREF and PROJECT
+after the project file PRJ-FILE-NAME is parsed."
+  nil)
+
+(cl-defgeneric wisi-xref-select-prj (_xref _project)
+  "PROJECT has been selected; do any xref-specific actions required."
+  nil)
+
+(cl-defgeneric wisi-xref-deselect-prj (_xref _project)
+  "PROJECT has been de-selected; undo any xref-specific select actions."
+  nil)
+
+(cl-defgeneric wisi-xref-refresh-cache (_xref _project _no-full)
+  "Refresh cached information in XREF. If no-full is non-nil,
+slow refresh operations may be skipped."
+  nil)
+
+(cl-defgeneric wisi-xref-definitions (_xref project item)
+  "Return all definitions (classwide) of ITEM (an xref-item), as a list of 
xref-items.")
+
+(cl-defgeneric wisi-xref-references (_xref project item)
+  "Return all references to ITEM (an xref-item), as a list of xref-items.")
+
+(cl-defgeneric wisi-xref-other (project &key identifier filename line column)
+  "Return cross reference information.
+PROJECT - dispatching object, normally a `wisi-prj' object.
+IDENTIFIER - an identifier or operator_symbol
+FILENAME - absolute filename containing the identifier
+LINE - line number containing the identifier (may be nil)
+COLUMN - Emacs column of the start of the identifier (may be nil)
+Point is on the start of the identifier.
+Returns a list (FILE LINE COLUMN) giving the corresponding location;
+FILE is an absolute file name.  If point is at the specification, the
+corresponding location is the
+body, and vice versa.")
+
+(defvar-local wisi-xref-full-path nil
+  "If non-nil, xref functions show full paths in results.")
+
+(defun wisi-goto-source (file line column)
+  "Find and select FILE, at LINE and COLUMN.
+FILE may be absolute, or on `compilation-search-path'.
+LINE, COLUMN are Emacs origin."
+  (let ((file-1
+        (if (file-name-absolute-p file) file
+          (ff-get-file-name compilation-search-path file))))
+    (if file-1
+       (setq file file-1)
+      (error "File %s not found; installed library, or set project?" file))
+    )
+
+  (push-mark (point) t)
+
+  (let ((buffer (get-file-buffer file)))
+    (cond
+     ((bufferp buffer)
+      ;; use pop-to-buffer, so package other-frame-window works.
+      (pop-to-buffer buffer (list #'display-buffer-same-window)))
+
+     ((file-exists-p file)
+      (find-file file))
+
+     (t
+      (error "'%s' not found" file))))
+
+  ;; move the cursor to the correct position
+  (goto-char (point-min))
+  (forward-line (1- line))
+  (forward-char column))
+
+(defun wisi-show-xref (xref)
+  "Display XREF location."
+  (let ((marker (xref-location-marker (xref-item-location xref))))
+    (pop-to-buffer (marker-buffer marker) (list #'display-buffer-same-window))
+    (goto-char (marker-position marker))))
+
+(defun wisi-get-identifier (prompt)
+  "Get identifier at point, or if no identifier at point, or with user arg, 
prompt for one."
+  ;; Similar to xref--read-identifier, but uses a different completion
+  ;; table, because we want a more specific reference.
+  (let* ((backend (xref-find-backend))
+         (def (xref-backend-identifier-at-point backend)))
+
+    (cond
+     ((or current-prefix-arg
+          (not def))
+      (let ((id
+             (completing-read
+              (if def
+                 (format "%s (default %s): " prompt def)
+                prompt)
+              (wisi-names t)
+              nil nil nil
+              'xref--read-identifier-history def)))
+        (if (equal id "")
+            (user-error "No identifier provided")
+          id)))
+     (t def))))
+
+(defun wisi-goto-spec/body (identifier)
+  "Goto declaration or body for IDENTIFIER (default symbol at point).
+If no symbol at point, or with prefix arg, prompt for symbol, goto spec."
+  (interactive (list (wisi-get-identifier "Goto spec/body of: ")))
+  (let ((prj (project-current)))
+    (wisi-show-xref
+     (wisi-xref-ident-make
+      identifier
+      (lambda (ident file line column)
+       (let ((target (wisi-xref-other
+                      (wisi-prj-xref prj) prj
+                      :identifier ident
+                      :filename file
+                      :line line
+                      :column column)))
+         (xref-make ident
+                    (xref-make-file-location
+                     (nth 0 target) ;; file
+                     (nth 1 target) ;; line
+                     (nth 2 target))) ;; column
+         ))))
+    ))
+
+(cl-defgeneric wisi-prj-identifier-at-point (_project)
+  "Return the identifier at point, move point to start of
+identifier.  Signal an error if no identifier is at point."
+  (let ((ident (thing-at-point 'symbol)))
+    (when ident
+      (skip-syntax-backward "w_")
+      ident)))
+
+(defun wisi-check-current-project (file-name &optional default-prj-function)
+  "If FILE-NAME (must be absolute) is found in the current
+project source directories, return the current
+project. Otherwise, if the current project is a wisi project,
+throw an error.  If the current project is not a wisi project,
+and DEFAULT-PRJ-FUNCTION is non-nil, use it to return a default
+project. Otherwise throw an error."
+  (let ((visited-file (file-truename file-name)) ;; file-truename handles 
symbolic links
+        (project (project-current)))
+    (if (wisi-prj-p project)
+       (let ((found-file (locate-file (file-name-nondirectory visited-file)
+                                      (wisi-prj-source-path project))))
+         (unless found-file
+           (error "current file not part of current project; wrong project?"))
+
+         (setq found-file (file-truename found-file))
+
+         ;; (nth 10 (file-attributes ...)) is the inode; required when hard
+         ;; links are present.
+         (let* ((visited-file-inode (nth 10 (file-attributes visited-file)))
+                (found-file-inode (nth 10 (file-attributes found-file))))
+           (unless (equal visited-file-inode found-file-inode)
+              (error "%s (opened) and %s (found in project) are two different 
files"
+                    file-name found-file)))
+         project)
+
+      ;; create a project?
+      (if default-prj-function
+         (funcall default-prj-function nil (file-name-directory file-name))
+       (error "current project is not a wisi project."))
+      )))
+
+(cl-defgeneric wisi-xref-parents (xref project &key identifier filename line 
column)
+  "Displays parent type declarations.
+XREF    - dispatching object.
+PROJECT - a `wisi-prj' object.
+IDENTIFIER - an identifier or operator_symbol
+FILENAME - absolute filename containing the identifier
+LINE - line number containing the identifier
+COLUMN - Emacs column of the start of the identifier
+
+Displays a buffer in compilation-mode giving locations of the parent type 
declarations.")
+
+(defun wisi-show-declaration-parents ()
+  "Display the locations of the parent type declarations of the type 
identifier around point."
+  (interactive)
+  (let ((project (wisi-check-current-project (buffer-file-name))))
+    (wisi-xref-parents
+     (wisi-prj-xref project)
+     project
+     :identifier (wisi-prj-identifier-at-point project)
+     :filename (file-name-nondirectory (buffer-file-name))
+     :line (line-number-at-pos)
+     :column (current-column))
+    ))
+
+(cl-defgeneric wisi-xref-all (xref project &key identifier filename line 
column local-only append)
+  "Displays cross reference information.
+XREF    - dispatching object.
+PROJECT - a `wisi-prj' object.
+IDENTIFIER - an identifier or operator_symbol (a string).
+FILENAME - absolute filename containing the identifier
+LINE - line number containing the identifier
+COLUMN - Emacs column of the start of the identifier
+LOCAL-ONLY - if t, show references in FILE only
+APPEND - if t, keep previous output in result buffer
+Displays a buffer in compilation-mode giving locations where the
+identifier is declared or referenced.")
+
+(defun wisi-show-references (&optional append)
+  "Show all references of identifier at point.
+With prefix, keep previous references in output buffer."
+  (interactive "P")
+  (let ((project (wisi-check-current-project (buffer-file-name))))
+    (wisi-xref-all
+     (wisi-prj-xref project)
+     project
+     :identifier (wisi-prj-identifier-at-point project)
+     :filename (file-name-nondirectory (buffer-file-name))
+     :line (line-number-at-pos)
+     :column (current-column)
+     :local-only nil
+     :append append)
+    ))
+
+(defun wisi-show-local-references (&optional append)
+  "Show all references of identifier at point occuring in current file.
+With prefix, keep previous references in output buffer."
+  (interactive "P")
+  (let ((project (wisi-check-current-project (buffer-file-name))))
+    (wisi-xref-all
+     (wisi-prj-xref project)
+     project
+     :identifier (wisi-prj-identifier-at-point project)
+     :filename (file-name-nondirectory (buffer-file-name))
+     :line (line-number-at-pos)
+     :column (current-column)
+     :local-only t
+     :append append)
+    ))
+
+(cl-defgeneric wisi-xref-overriding (xref project &key identifier filename 
line column)
+  "Displays a buffer in compilation-mode giving locations of the overriding 
declarations.
+XREF    - dispatching object.
+PROJECT - a `wisi-prj' object.
+IDENTIFIER - an identifier or operator_symbol
+FILENAME - filename containing the identifier
+LINE - line number containing the identifier
+COLUMN - Emacs column of the start of the identifier ")
+
+(defun wisi-show-overriding ()
+  "Show all overridings of identifier at point."
+  (interactive)
+  (let ((project (wisi-check-current-project (buffer-file-name))))
+    (wisi-xref-overriding
+     (wisi-prj-xref project)
+     project
+     :identifier (wisi-prj-identifier-at-point project)
+     :filename (file-name-nondirectory (buffer-file-name))
+     :line (line-number-at-pos)
+     :column (current-column))
+    ))
+
+(cl-defgeneric wisi-xref-overridden (xref project &key identifier filename 
line column)
+  "Returns a list (FILE LINE COLUMN) giving the location of the overridden 
declaration.
+XREF    - dispatching object.
+PROJECT - a `wisi-prj' object.
+IDENTIFIER - an identifier or operator_symbol
+FILENAME - absolute filename containing the identifier
+LINE - line number containing the identifier
+COLUMN - Emacs column of the start of the identifier")
+
+(defun wisi-show-overridden ()
+  "Show the overridden declaration of identifier at point."
+  (interactive)
+  (let* ((project (wisi-check-current-project (buffer-file-name)))
+        (target
+         (wisi-xref-overridden
+          (wisi-prj-xref project)
+          project
+          :identifier (wisi-prj-identifier-at-point project)
+          :filename (file-name-nondirectory (buffer-file-name))
+          :line (line-number-at-pos)
+          :column (current-column))))
+
+    (wisi-goto-source (nth 0 target)
+                     (nth 1 target)
+                     (nth 2 target))
+  ))
+
+;;;; wisi-prj specific methods
+
+(cl-defmethod project-roots ((_project wisi-prj))
+  ;; Not meaningful
+  nil)
+
+(cl-defmethod project-files ((project wisi-prj) &optional dirs)
+  (let (result)
+    (dolist (dir (or dirs
+                    (wisi-prj-source-path project)))
+      (mapc
+       (lambda (absfile)
+        (when (and (not (string-equal "." (substring absfile -1)))
+                   (not (string-equal ".." (substring absfile -2)))
+                   (not (file-directory-p absfile))
+                    (or (null (wisi-prj-file-pred project))
+                       (funcall (wisi-prj-file-pred project) absfile)))
+          (push absfile result)))
+       (when (file-readable-p dir) ;; GNAT puts non-existing dirs on path.
+        (directory-files dir t))))
+    result))
+
+(defun wisi-refresh-prj-cache (not-full)
+  "Refresh all cached data in the current project, and re-select it.
+With prefix arg, very slow refresh operations may be skipped."
+  (interactive "P")
+  (let ((prj (project-current)))
+    (unless (wisi-prj-p prj)
+      (error "current project is not a wisi project"))
+    (wisi-prj-refresh-cache prj not-full)
+    (wisi-prj-select prj)))
+
+(defvar wisi-prj--current-file nil
+  "Current wisi project file (the most recently selected); an
+absolute file name.")
+
+(defun wisi-prj-clear-current ()
+  "Clear the current project selection; make no project current."
+  (interactive)
+  (setq wisi-prj--current-file nil))
+
+(defun wisi-prj-show ()
+  "Show name of current project."
+  (interactive)
+  (message
+   (cond
+    (wisi-prj--current-file
+     (wisi-prj-name (cdr (assoc wisi-prj--current-file wisi-prj--cache))))
+    (t
+     (let ((prj (project-current)))
+       (if (wisi-prj-p prj)
+          (wisi-prj-name prj)
+        "not a wisi project"))))))
+
+(cl-defmethod wisi-prj-parse-final (project _prj-file)
+  (wisi--case-read-all-exceptions project))
+
+(cl-defmethod wisi-prj-refresh-cache ((project wisi-prj) not-full)
+  (when wisi-prj--cache
+    (wisi-prj-deselect project)
+    (let ((prj-file (car (rassoc project wisi-prj--cache))))
+       (setq wisi-prj--cache (delete (cons prj-file project) wisi-prj--cache))
+       (setq project (wisi-prj-default project))
+       (wisi-prj-parse-file :prj-file prj-file :init-prj project :cache t)
+       (wisi-prj-select project)))
+  (wisi-xref-refresh-cache (wisi-prj-xref project) project not-full))
+
+(cl-defmethod wisi-prj-select ((project wisi-prj))
+  (setq compilation-search-path (wisi-prj-source-path project))
+
+  ;; ‘compilation-environment’ is buffer-local, but the user might
+  ;; delete that buffer. So set both global and local.
+  (let ((comp-env
+        (append
+         (wisi-prj-compile-env project)
+         (wisi-prj-file-env project)
+         (copy-sequence (wisi-prj-file-env project))))
+       (comp-buf (get-buffer "*compilation*")))
+    (when (buffer-live-p comp-buf)
+      (with-current-buffer comp-buf
+       (setq compilation-environment comp-env)))
+    (set-default 'compilation-environment comp-env))
+
+  (wisi-compiler-select-prj (wisi-prj-compiler project) project)
+  (wisi-xref-select-prj     (wisi-prj-xref project)     project))
+
+(cl-defmethod wisi-prj-deselect ((project wisi-prj))
+  (wisi-xref-deselect-prj (wisi-prj-xref project) project)
+  (wisi-compiler-deselect-prj (wisi-prj-compiler project) project)
+  (setq compilation-environment nil)
+  (setq compilation-search-path nil))
+
+(defvar wisi-prj-parse-hook nil
+  "Hook run at start of `wisi-prj-parse-file'.")
+
+(defvar wisi-prj-parser-alist (list (cons "prj" #'wisi-prj-parse-file-1))
+  "Alist of parsers for project files, indexed by file extension.
+Parser is called with two arguments; the project file name and
+a project. Parser should update the project with values from the file.")
+
+(cl-defmethod wisi-prj-parse-one (project name value)
+  "If NAME is a wisi-prj slot, set it to VALUE, return t.
+Else return nil."
+  (cond
+   ((string= name "casing")
+    (cl-pushnew (expand-file-name
+                 (substitute-in-file-name value))
+                (wisi-prj-case-exception-files project)
+               :test #'string-equal)
+    t)
+
+   ((string= name "src_dir")
+    (cl-pushnew (directory-file-name (expand-file-name 
(substitute-in-file-name value)))
+                (wisi-prj-source-path project)
+               :test #'string-equal)
+    t)
+
+   ((= ?$ (elt name 0))
+    ;; Process env var.
+    (setf (wisi-prj-file-env project)
+         (cons (concat (substring name 1) "=" (substitute-in-file-name value))
+               (wisi-prj-file-env project)))
+    t)
+
+   ))
+
+(defvar-local wisi-prj-parse-undefined-function nil
+  "Function called if a project file variable name is not recognized.
+Called with three args: PROJECT NAME VALUE.")
+
+(defun wisi-prj-parse-file-1 (prj-file project)
+  "Wisi project file parser."
+  (with-current-buffer (find-file-noselect prj-file)
+    (goto-char (point-min))
+
+    ;; process each line
+    (while (not (eobp))
+
+      ;; ignore lines that don't have the format "name=value", put
+      ;; 'name', 'value' in match-string.
+      (when (looking-at "^\\([^= \n]+\\)=\\(.*\\)")
+       (let ((name (match-string 1))
+             (value (match-string 2))
+             result)
+
+         ;; Both compiler and xref need to see some settings; eg gpr_file, env 
vars.
+         (when (wisi-compiler-parse-one (wisi-prj-compiler project) project 
name value)
+           (setq result t))
+         (when (wisi-xref-parse-one (wisi-prj-xref project) project name value)
+           (setq result t))
+
+         (unless result
+           (setq result (wisi-prj-parse-one project name value)))
+
+         (when (and (not result)
+                    wisi-prj-parse-undefined-function)
+           (funcall wisi-prj-parse-undefined-function project name value))
+
+          ))
+
+      (forward-line 1)
+      )
+    ))
+
+(cl-defun wisi-prj-parse-file (&key prj-file init-prj cache)
+  "Read project file PRJ-FILE with default values from INIT-PRJ.
+PRJ-FILE parser is from `wisi-prj-parser-alist'; if that yields
+no parser, no error occurs; the file is just a placeholder.  If
+CACHE is non-nil, add the project to `wisi-prj--cache'.  In any
+case, return the project."
+  (setq prj-file (expand-file-name prj-file))
+
+  (run-hooks 'wisi-prj-parse-hook)
+
+  (let* ((default-directory (file-name-directory prj-file))
+        (parser (cdr (assoc (file-name-extension prj-file) 
wisi-prj-parser-alist)))
+        (project init-prj)
+        (process-environment (append (wisi-prj-compile-env init-prj) 
process-environment)))
+
+    (when parser
+      ;; If no parser, prj-file is just a placeholder; there is no file to 
parse.
+      ;; For example, sal-android-prj has no project file.
+       (funcall parser prj-file project)
+       (wisi-prj-parse-final project prj-file)
+       (wisi-compiler-parse-final (wisi-prj-compiler project) project prj-file)
+       (wisi-xref-parse-final (wisi-prj-xref project) project prj-file))
+
+    (when cache
+      ;; Cache the project properties
+      (if (assoc prj-file wisi-prj--cache)
+         (setcdr (assoc prj-file wisi-prj--cache) project)
+       (push (cons prj-file project) wisi-prj--cache)))
+
+    project))
+
+(defun wisi-prj-show-prj-path ()
+  "Show the compiler project file search path."
+  (interactive)
+  (wisi-compiler-show-prj-path (wisi-prj-compiler (wisi-prj-require-prj))))
+
+(defun wisi-prj-show-src-path ()
+  "Show the project source file search path."
+  (interactive)
+  (if compilation-search-path
+      (progn
+       (pop-to-buffer (get-buffer-create "*source file search path*"))
+       (erase-buffer)
+       (dolist (file compilation-search-path)
+         (insert (format "%s\n" file))))
+    (message "no source file search path set")
+    ))
+
+(defun wisi-fix-compiler-error ()
+  "Attempt to fix the current compiler error.
+Point must be at the source location referenced in a compiler error.
+In `compilation-last-buffer', point must be at the compiler error.
+Leave point at fixed code."
+  (interactive)
+  (let ((source-buffer (current-buffer))
+       (line-move-visual nil)); screws up next-line otherwise
+
+    (cond
+     ((equal compilation-last-buffer wisi-error-buffer)
+      (set-buffer source-buffer)
+      (wisi-repair-error))
+
+     (t
+      (with-current-buffer compilation-last-buffer
+       (let ((comp-buf-pt (point))
+             (success
+              (wisi-compiler-fix-error
+               (wisi-prj-compiler (wisi-prj-require-prj))
+               source-buffer)))
+         ;; restore compilation buffer point
+         (set-buffer compilation-last-buffer)
+         (goto-char comp-buf-pt)
+
+         (unless success
+           (error "error not recognized"))
+         )))
+      )))
+
+;;;; auto-casing
+
+(defvar-local wisi-auto-case nil
+  "Buffer-local value indicating whether to change case while typing.
+When non-nil, automatically change case of preceding word while
+typing.  Casing of keywords is done according to
+`wisi-case-keyword', identifiers according to
+`wisi-case-identifier'."
+  ;; This is not a defcustom, because it's buffer-local.
+  )
+
+(defvar-local wisi-case-keyword 'lower-case
+  "Indicates how to adjust the case of `wisi-keywords'.
+Value is one of lower-case, upper-case."
+  ;; This is not a defcustom, because it's buffer-local
+  )
+
+(defvar-local wisi-case-identifier 'mixed-case
+  "Buffer-local value indicating how to case language keywords.
+Value is one of:
+
+- mixed-case : Mixed_Case
+- lower-case : lower_case
+- upper-case : UPPER_CASE")
+
+(defvar-local wisi-case-strict t
+  "If nil, preserve uppercase chars in identifiers.")
+
+(defvar-local wisi-language-keywords nil
+  "List of keywords for auto-case.")
+
+(defvar-local wisi-case-adjust-p-function nil
+  "Function taking one argument, the typed char; called from wisi-case-adjust.
+Return non-nil if case of symbol at point should be adjusted.
+Point is on last char of symbol.")
+
+(defun wisi-case-show-files ()
+  "Show casing files list for the current project."
+  (interactive)
+  (let ((project (project-current)))
+
+    (if (and (wisi-prj-p project)
+            (wisi-prj-case-exception-files project))
+       (progn
+         (pop-to-buffer (get-buffer-create "*casing files*"))
+         (erase-buffer)
+         (dolist (file (wisi-prj-case-exception-files project))
+           (insert (format "%s\n" file))))
+      (message "no casing files")
+      )))
+
+(defun wisi--case-save-exceptions (full-exceptions partial-exceptions 
file-name)
+  "Save FULL-EXCEPTIONS, PARTIAL-EXCEPTIONS to the file FILE-NAME."
+  ;; If there is a buffer visiting file-name, it may be out of date
+  ;; due to a previous save-exceptions, which will give a user prompt
+  ;; about editing a file that has changed on disk. Update the buffer
+  (let ((buf (get-file-buffer file-name)))
+    (when buf
+      (with-current-buffer buf
+                       (revert-buffer nil t t))))
+
+  (with-temp-file (expand-file-name file-name)
+    (mapc (lambda (x) (insert (car x) "\n"))
+         (sort (copy-sequence full-exceptions)
+               (lambda(a b) (string< (car a) (car b)))))
+    (mapc (lambda (x) (insert "*" (car x) "\n"))
+         (sort (copy-sequence partial-exceptions)
+               (lambda(a b) (string< (car a) (car b)))))
+    ))
+
+(defun wisi--case-read-exceptions (file-name)
+  "Read the content of the casing exception file FILE-NAME.
+Return (cons full-exceptions partial-exceptions)."
+  (setq file-name (expand-file-name (substitute-in-file-name file-name)))
+  (if (file-readable-p file-name)
+      (let (full-exceptions partial-exceptions word)
+       (with-temp-buffer
+         (insert-file-contents file-name)
+         (while (not (eobp))
+
+           (setq word (buffer-substring-no-properties
+                       (point) (save-excursion (skip-syntax-forward "w_") 
(point))))
+
+           (if (char-equal (string-to-char word) ?*)
+               ;; partial word exception
+               (progn
+                 (setq word (substring word 1))
+                 (unless (assoc-string word partial-exceptions t)
+                   (push (cons word t) partial-exceptions)))
+
+             ;; full word exception
+             (unless (assoc-string word full-exceptions t)
+               (push (cons word t) full-exceptions)))
+
+           (forward-line 1))
+         )
+       (cons full-exceptions partial-exceptions))
+
+    ;; else file not readable; might be a new project with no
+    ;; exceptions yet, so just return empty pair
+    (message "'%s' is not a readable file." file-name)
+    '(nil . nil)
+    ))
+
+(defun wisi--case-merge-exceptions (result new)
+  "Merge NEW exeptions into RESULT.
+An item in both lists has the RESULT value."
+  (dolist (item new)
+    (unless (assoc-string (car item) result t)
+      (push item result)))
+  result)
+
+(defun wisi--case-merge-all-exceptions (exceptions project)
+  "Merge EXCEPTIONS into PROJECT case-full-exceptions, 
case-partial-exceptions."
+  (setf (wisi-prj-case-full-exceptions project)
+       (wisi--case-merge-exceptions (wisi-prj-case-full-exceptions project)
+                                    (car exceptions)))
+  (setf (wisi-prj-case-partial-exceptions project)
+       (wisi--case-merge-exceptions (wisi-prj-case-partial-exceptions project)
+                                    (cdr exceptions))))
+
+(defun wisi--case-read-all-exceptions (project)
+  "Read case exceptions from all files in PROJECT casing files."
+  (setf (wisi-prj-case-full-exceptions project) '())
+  (setf (wisi-prj-case-partial-exceptions project) '())
+
+  (dolist (file (wisi-prj-case-exception-files project))
+    (wisi--case-merge-all-exceptions (wisi--case-read-exceptions file) 
project)))
+
+(defun wisi--case-add-exception (word exceptions)
+  "Add case exception WORD to EXCEPTIONS, replacing current entry, if any."
+  (if (assoc-string word exceptions t)
+      (setcar (assoc-string word exceptions t) word)
+    (push (cons word t) exceptions))
+  exceptions)
+
+(defun wisi-case-create-exception (&optional partial)
+  "Define a word as an auto-casing exception in the current project.
+The word is the active region, or the symbol at point.  If
+PARTIAL is non-nil, create a partial word exception.  User is
+prompted to choose a file from the project case-exception-files
+if it is a list."
+  (interactive)
+  (let* ((project (wisi-prj-require-prj))
+        (file-name
+        (cond
+          ((< 1 (length (wisi-prj-case-exception-files project)))
+           (completing-read "case exception file: " 
(wisi-prj-case-exception-files project)
+                            nil ;; predicate
+                            t   ;; require-match
+                            nil ;; initial-input
+                            nil ;; hist
+                            (car (wisi-prj-case-exception-files project)) ;; 
default
+                            ))
+
+          ((= 1 (length (wisi-prj-case-exception-files project)))
+           (car (wisi-prj-case-exception-files project)))
+
+          (t
+           (error "No exception file specified; set `casing' in project 
file."))
+          ))
+       word)
+
+    (if (use-region-p)
+       (progn
+         (setq word (buffer-substring-no-properties (region-beginning) 
(region-end)))
+         (deactivate-mark))
+      (save-excursion
+       (let ((syntax (if partial "w" "w_")))
+         (skip-syntax-backward syntax)
+         (setq word
+               (buffer-substring-no-properties
+                (point)
+                (progn (skip-syntax-forward syntax) (point))
+                )))))
+
+    (let* ((exceptions (wisi--case-read-exceptions file-name))
+          (file-full-exceptions (car exceptions))
+          (file-partial-exceptions (cdr exceptions)))
+
+      (cond
+       ((null partial)
+       (setf (wisi-prj-case-full-exceptions project)
+             (wisi--case-add-exception word (wisi-prj-case-full-exceptions 
project)))
+       (setq file-full-exceptions (wisi--case-add-exception word 
file-full-exceptions)))
+
+       (t
+       (setf (wisi-prj-case-partial-exceptions project)
+             (wisi--case-add-exception word (wisi-prj-case-partial-exceptions 
project)))
+       (setq file-partial-exceptions (wisi--case-add-exception word 
file-partial-exceptions)))
+
+       )
+      (wisi--case-save-exceptions file-full-exceptions file-partial-exceptions 
file-name)
+      (message "created %s case exception '%s' in file '%s'"
+              (if partial "partial" "full")
+              word
+              file-name)
+      )
+    ))
+
+(defun wisi-case-create-partial-exception ()
+  "Define active region or word at point as a partial word exception.
+User is prompted to choose a file from the project
+case-exception-files if it is a list."
+  (interactive)
+  (wisi-case-create-exception t))
+
+(defun wisi-after-keyword-p ()
+  "Return non-nil if point is after an element of `wisi-language-keywords'."
+  (let ((word (buffer-substring-no-properties
+              (save-excursion (skip-syntax-backward "w_") (point))
+              (point))))
+    (member (downcase word) wisi-language-keywords)))
+
+(defvar-local wisi--ret-binding #'wisi-indent-newline-indent)
+(defvar-local wisi--lfd-binding #'newline-and-indent)
+
+(defun wisi-case-keyword (beg end)
+  (cl-ecase wisi-case-keyword
+    (lower-case (downcase-region beg end))
+    (upper-case (upcase-region beg end))
+    ))
+
+(defun wisi-case-identifier (start end case-strict)
+  (cl-ecase wisi-case-identifier
+    (mixed-case (wisi-mixed-case start end case-strict))
+    (lower-case (downcase-region start end))
+    (upper-case (upcase-region start end))
+    ))
+
+(defun wisi-mixed-case (start end case-strict)
+  "Adjust case of region START END to Mixed_Case."
+  (let ((done nil)
+       next)
+    (if (or case-strict wisi-case-strict)
+       (downcase-region start end))
+    (goto-char start)
+    (while (not done)
+      (setq next
+           (or
+            (save-excursion (when (search-forward "_" end t) (point-marker)))
+            (copy-marker (1+ end))))
+
+      ;; upcase first char
+      (upcase-region (point) (1+ (point)))
+
+      (goto-char next)
+      (if (< (point) end)
+         (setq start (point))
+       (setq done t))
+      )))
+
+(defun wisi-case-adjust-identifier (&optional force-case)
+  "Adjust case of the previous word as an identifier.
+Uses `wisi-case-identifier', with exceptions defined in
+`wisi-case-full-exceptions', `wisi-case-partial-exceptions'.  If
+force-case non-nil (default prefix), treat `wisi-strict-case' as
+t."
+  (interactive "P")
+  (save-excursion
+    ;; We don't complain when there is no project; we may be editing a
+    ;; random Ada file.
+    (let ((prj (wisi-prj-current-prj))
+         (end   (point-marker))
+         (start (progn (skip-syntax-backward "w_") (point)))
+         match
+         next
+         (done nil))
+
+      (if (setq match
+               (assoc-string (buffer-substring-no-properties start end)
+                             (wisi-prj-case-full-exceptions prj)
+                             t ;; case-fold
+                             ))
+         ;; full word exception
+         (progn
+           ;; 'save-excursion' puts a marker at 'end'; if we do
+           ;; 'delete-region' first, it moves that marker to 'start',
+           ;; then 'insert' inserts replacement text after the
+           ;; marker, defeating 'save-excursion'. So we do 'insert' first.
+           (insert (car match))
+           (delete-region (point) end))
+
+       ;; else apply wisi-case-identifier
+       (wisi-case-identifier start end force-case)
+
+       ;; apply partial-exceptions
+       (goto-char start)
+       (while (not done)
+         (setq next
+               (or
+                (save-excursion (when (search-forward "_" end t) 
(point-marker)))
+                (copy-marker (1+ end))))
+
+         (when (setq match (assoc-string (buffer-substring-no-properties start 
(1- next))
+                                         (wisi-prj-case-partial-exceptions prj)
+                                         t))
+           ;; see comment above at 'full word exception' for why
+           ;; we do insert first.
+           (insert (car match))
+           (delete-region (point) (1- next)))
+
+         (goto-char next)
+         (if (< (point) end)
+             (setq start (point))
+           (setq done t))
+          ))
+      )))
+
+(defun wisi-case-adjust-keyword ()
+  "Adjust the case of the previous symbol as a keyword."
+  (save-excursion
+    (let ((end   (point-marker))
+         (start (progn (skip-syntax-backward "w_") (point))))
+      (wisi-case-keyword start end)
+    )))
+
+(defun wisi-case-adjust (&optional typed-char in-comment)
+  "Adjust the case of the symbol before point.
+When invoked interactively, TYPED-CHAR must be
+`last-command-event', and it must not have been inserted yet.  If
+IN-COMMENT is non-nil, adjust case of words in comments and
+strings as code, and treat `wisi-case-strict' as t in code."
+  (when (not (bobp))
+    (when (save-excursion
+           (forward-char -1); back to last character in symbol
+           (and (not (bobp))
+                (eq (char-syntax (char-after)) ?w); it can be capitalized
+
+                (or in-comment
+                    (not (wisi-in-string-or-comment-p)))
+
+                (or (null wisi-case-adjust-p-function)
+                    (funcall wisi-case-adjust-p-function typed-char))
+                ))
+
+      ;; The indentation engine may trigger a reparse on
+      ;; non-whitespace changes, but we know we don't need to reparse
+      ;; for this change (assuming the user has not abused case
+      ;; exceptions!).
+      (let ((inhibit-modification-hooks t))
+       (cond
+        ;; Some attributes are also keywords, but captialized as
+        ;; attributes. So check for attribute first.
+        ((and
+          (not in-comment)
+          (save-excursion
+            (skip-syntax-backward "w_")
+            (eq (char-before) ?')))
+         (wisi-case-adjust-identifier in-comment))
+
+        ((and
+          (not in-comment)
+          (not (eq typed-char ?_))
+          (wisi-after-keyword-p))
+         (wisi-case-adjust-keyword))
+
+        (t (wisi-case-adjust-identifier in-comment))
+        ))
+      )))
+
+(defun wisi-case-adjust-at-point (&optional in-comment)
+  "If ’wisi-auto-case’ is non-nil, adjust case of symbol at point.
+Also move to end of symbol.  With prefix arg, adjust case as code
+even if in comment or string; otherwise, capitalize words in
+comments and strings.  If ’wisi-auto-case’ is nil, capitalize
+current word."
+  (interactive "P")
+  (cond
+   ((or (null wisi-auto-case)
+       (and (not in-comment)
+            (wisi-in-string-or-comment-p)))
+    (skip-syntax-backward "w_")
+    (capitalize-word 1))
+
+   (t
+    (when
+       (and (not (eobp))
+            ;; We use '(syntax-after (point))' here, not '(char-syntax
+            ;; (char-after))', because the latter does not respect
+            ;; syntax-propertize functions
+            (memq (syntax-class (syntax-after (point))) '(2 3)))
+      (skip-syntax-forward "w_"))
+    (wisi-case-adjust nil in-comment))
+   ))
+
+(defun wisi-case-adjust-region (begin end)
+  "Adjust case of all words in region BEGIN END."
+  (interactive "r")
+  (narrow-to-region begin end)
+  (save-excursion
+    (goto-char begin)
+    (while (not (eobp))
+      (forward-comment (point-max))
+      (skip-syntax-forward "^w_")
+      (skip-syntax-forward "w_")
+      (wisi-case-adjust)))
+  (widen))
+
+(defun wisi-case-adjust-buffer ()
+  "Adjust case of current buffer."
+  (interactive)
+  (wisi-case-adjust-region (point-min) (point-max)))
+
+(defun wisi-case-adjust-interactive (arg)
+  "If `wisi-auto-case' is non-nil, adjust the case of the previous symbol,
+and process the character just typed.  To be bound to keys that
+should cause auto-casing.  ARG is the prefix the user entered
+with \\[universal-argument]."
+  (interactive "P")
+
+  ;; Character typed has not been inserted yet.
+  (let ((lastk last-command-event)
+       (do-adjust nil))
+    (cond
+     ((null wisi-auto-case))
+     (t
+      (setq do-adjust t)))
+
+    (cond
+     ((eq lastk ?\n)
+        (when do-adjust
+         (wisi-case-adjust lastk))
+       (funcall wisi--lfd-binding))
+
+     ((memq lastk '(?\r return))
+      (when do-adjust
+       (wisi-case-adjust lastk))
+      (funcall wisi--ret-binding))
+
+     (t
+      (when do-adjust
+       (wisi-case-adjust lastk))
+      (self-insert-command (prefix-numeric-value arg)))
+     )))
+
+(defun wisi-case-activate-keys (map)
+  "Modify the key bindings for all the keys that should adjust casing."
+  (mapc (function
+        (lambda(key)
+          (define-key
+            map
+            (char-to-string key)
+            'wisi-case-adjust-interactive)))
+       '( ?_ ?% ?& ?* ?\( ?\) ?- ?= ?+
+             ?| ?\; ?: ?' ?\" ?< ?, ?. ?> ?/ ?\n 32 ?\r ))
+  )
+
+;;;; xref backend
+
+(cl-defmethod xref-backend-definitions ((prj wisi-prj) identifier)
+  (wisi-xref-definitions (wisi-prj-xref prj) prj (wisi-xref-item identifier)))
+
+(cl-defmethod xref-backend-identifier-at-point ((prj wisi-prj))
+  (save-excursion
+    (condition-case nil
+       (let ((ident (wisi-prj-identifier-at-point prj))) ;; moves point to 
start of ident
+         (put-text-property
+          0 1
+          'xref-identifier
+          (list ':file (buffer-file-name)
+                ':line (line-number-at-pos)
+                ':column (current-column))
+          ident)
+         ident)
+       (error
+        ;; from wisi-prj-identifier-at-point; no identifier
+        nil))))
+
+(cl-defmethod xref-backend-identifier-completion-table ((_prj wisi-prj))
+  ;; Current buffer only.
+  (wisi-names nil))
+
+(cl-defmethod xref-backend-references  ((prj wisi-prj) identifier)
+  (wisi-xref-references (wisi-prj-xref prj) prj (wisi-xref-item identifier)))
+
+;;;###autoload
+(defun wisi-prj-xref-backend ()
+  "For `xref-backend-functions'; return the current wisi project."
+  ;; We return the project, not the xref object, because the
+  ;; wisi-xref-* functions need the project.
+  (let ((prj (project-current)))
+    (when (wisi-prj-p prj)
+      prj)))
+
+;;;; project-find-functions alternatives
+
+(defvar wisi-prj--dominating-alist nil
+"Alist of (DOMINATING-FILE . PRJ-FILE-NAME): DOMINATING-FILE is
+an absolute filename that can be found by
+`wisi-prj-find-dominating-cached' or
+`wisi-prj-find-dominating-cached'.  PRJ-FILE-NAME is the wisi
+project file for the project for that file.")
+
+;;;###autoload
+(defun wisi-prj-select-cache (prj-file init-prj &optional dominating-file)
+  "Select project matching PRJ-FILE in `wisi-prj--cache' as current project,
+parsing and caching if needed. Also add DOMINATING-FILE (default
+current buffer file name) to `wisi-prj--dominating-alist' (for
+`wisi-prj-select-dominating'.)"
+  (let ((old-prj (project-current)))
+    ;; If old-prj is not a wisi-prj, we don't know how to deselect it;
+    ;; just ignore that.  If prj-file is the current file, user is
+    ;; re-selecting it.
+    (when (wisi-prj-p old-prj)
+      (wisi-prj-deselect old-prj)))
+
+  (unless (or (memq #'wisi-prj-current-cached project-find-functions)
+             (memq #'wisi-prj-current-cached (default-value 
'project-find-functions)))
+    (message "wisi-prj-select-cache used without wisi-prj-current-cached in 
project-find-functions"))
+
+  (setq dominating-file (if dominating-file (expand-file-name dominating-file) 
(buffer-file-name)))
+  (setq prj-file (expand-file-name prj-file))
+  (add-to-list 'wisi-prj--dominating-alist (cons dominating-file prj-file))
+
+  (let ((new-prj (cdr (assoc prj-file wisi-prj--cache))))
+    (unless new-prj
+      (setq new-prj (wisi-prj-parse-file :prj-file prj-file :init-prj init-prj 
:cache t))
+      (unless new-prj
+       (error "parsing project file '%s' failed" prj-file)))
+
+    (setq wisi-prj--current-file prj-file)
+    (wisi-prj-select new-prj)))
+
+;;;###autoload
+(defun wisi-prj-select-dominating (&optional dominating-file)
+  "Unless it is already current, select a wisi-prj matching DOMINATING-FILE.
+DOMINATING-FILE defaults to the current buffer file name.
+Useful before running `compilation-start', to ensure the correct
+project is current."
+  (when (or dominating-file (buffer-file-name))
+    ;; buffer-file-name is nil in *compilation* buffer
+    (let ((prj-file (cdr (assoc (or dominating-file (buffer-file-name)) 
wisi-prj--dominating-alist))))
+      (unless (string-equal prj-file wisi-prj--current-file)
+       (message "Switching to project file '%s'" prj-file)
+       (let ((old-prj (cdr (assoc  wisi-prj--current-file wisi-prj--cache)))
+             (new-prj (cdr (assoc prj-file wisi-prj--cache))))
+         (when (wisi-prj-p old-prj)
+           (wisi-prj-deselect old-prj))
+         (when (wisi-prj-p new-prj)
+           (wisi-prj-select new-prj))
+         (setq wisi-prj--current-file prj-file))))))
+
+;;;###autoload
+(defun wisi-prj-current-cached (_dir)
+  "For `project-find-functions'; return the current project from 
`wisi-prj--cache'."
+  (cdr (assoc wisi-prj--current-file wisi-prj--cache)))
+
+(defvar wisi-prj--default nil
+  "Alist of (PRJ-FILE . INIT-PRJ), for `wisi-prj-parse-current'.
+PRJ-FILE is an absolute project file name; INIT-PRJ is the
+initial `wisi-prj' object for that project file.")
+
+;;;###autoload
+(defun wisi-prj-select-file (prj-file default-prj &optional dominating-file)
+  "Set PRJ-FILE as current project, add DEFAULT-PRJ to `wisi-prj--default'.
+Also add DOMINATING-FILE (default current buffer file name) to
+`wisi-prj--dominating-alist' (for `wisi-prj-select-dominating'.)"
+  (unless (or (memq #'wisi-prj-current-parse project-find-functions)
+             (memq #'wisi-prj-current-parse (default-value 
'project-find-functions)))
+    (message "wisi-prj-select-file used without wisi-prj-current-parse in 
project-find-functions"))
+
+  (setq dominating-file (if dominating-file (expand-file-name dominating-file) 
(buffer-file-name)))
+  (setq prj-file (expand-file-name prj-file))
+  (add-to-list 'wisi-prj--dominating-alist (cons dominating-file prj-file))
+  (setq wisi-prj--current-file prj-file)
+  (add-to-list 'wisi-prj--default (cons prj-file default-prj)))
+
+;;;###autoload
+(defun wisi-prj-current-parse (_dir)
+  "For `project-find-functions'; parse the current project file, select and 
return the project"
+  (let ((prj (wisi-prj-parse-file
+             :prj-file wisi-prj--current-file
+             :init-prj (cdr (assoc-string wisi-prj--current-file 
wisi-prj--default))
+             :cache nil)))
+    (wisi-prj-select prj)
+    prj))
+
+(defvar wisi-prj--dominating nil
+  "List of relative filenames for `wisi-prj-find-dominating-cached'
+and `wisi-prj-find-dominating-parse'. Set by `wisi-prj-set-dominating'.")
+
+(defun wisi-prj-reset-cache ()
+  "Delete all wisi project cached info."
+  (interactive)
+  (setq wisi-prj--cache nil)
+  (setq wisi-prj--current-file nil)
+  (setq wisi-prj--default nil)
+  (setq wisi-prj--dominating nil)
+  (setq wisi-prj--dominating-alist nil))
+
+;;;###autoload
+(defun wisi-prj-cache-dominating (prj-file default-prj &optional 
dominating-file)
+  "Parse prj-file, add to `wisi-prj--cache'.
+Also add (DOMINATING-FILE . PRJ-FILE) to `wisi-prj--dominating-alist'.
+DOMINATING-FILE defaults to (buffer-file-name). "
+  (unless (or (memq #'wisi-prj-find-dominating-cached project-find-functions)
+             (memq #'wisi-prj-find-dominating-cached (default-value 
'project-find-functions)))
+    (message "wisi-prj-cache-dominating used without 
wisi-prj-find-dominating-cached in project-find-functions"))
+
+  (setq dominating-file (if dominating-file (expand-file-name dominating-file) 
(buffer-file-name)))
+  (setq prj-file (expand-file-name prj-file))
+  (add-to-list 'wisi-prj--dominating (file-name-nondirectory dominating-file))
+  (add-to-list 'wisi-prj--dominating-alist (cons dominating-file prj-file))
+  (wisi-prj-parse-file :prj-file prj-file :init-prj default-prj :cache t)
+  nil)
+
+(defun wisi-prj--find-dominating-file (start-dir)
+  "Return the project file matching `wisi-prj--dominating'."
+  (let* (dom-file
+        (_dom-dir
+         (locate-dominating-file
+          start-dir
+          (lambda (dir)
+            (let ((names wisi-prj--dominating))
+              (while names
+                (let ((filename (expand-file-name (pop names) dir)))
+                  (when (file-exists-p filename)
+                    (setq dom-file filename)
+                    (setq names nil)))))
+            dom-file))))
+    (cdr (assoc-string dom-file wisi-prj--dominating-alist))))
+
+;;;###autoload
+(defun wisi-prj-find-dominating-cached (dir)
+  "For `project-find-functions'; return the cached project
+matching `wisi-prj--dominating' (nil if none). Select it if it is
+not the current project."
+  (let* ((prj-file (wisi-prj--find-dominating-file dir))
+        (new-prj (cdr (assoc-string prj-file wisi-prj--cache))))
+    (when prj-file
+      (unless (string= prj-file wisi-prj--current-file)
+       (let ((old-prj (cdr (assoc-string wisi-prj--current-file 
wisi-prj--cache))))
+         (when old-prj (wisi-prj-deselect old-prj))
+         (unless new-prj
+           ;; User may have used `wisi-prj-set-dominating' instead of
+           ;; `wisi-prj-cache-dominating'; parse the project file now.
+           (wisi-prj-parse-file
+            :prj-file prj-file
+            :init-prj (cdr (assoc-string prj-file wisi-prj--default))
+            :cache t))
+         (when new-prj (wisi-prj-select new-prj))))
+      new-prj)))
+
+;;;###autoload
+(defun wisi-prj-set-dominating (prj-file default-prj &optional dom-file)
+  "Add (DOM-FILE . PRJ-FILE) to `wisi-prj--dominating-alist',
+and (PRJ-FILE . DEFAULT-PRJ) to `wisi-prj--default'.
+DOM-FILE defaults to (buffer-file-name).
+For example, call this in the Local Vars of a Makefile to
+associate a project with that Makefile."
+  (unless (or (memq #'wisi-prj-find-dominating-parse project-find-functions)
+             (memq #'wisi-prj-find-dominating-parse (default-value 
'project-find-functions)))
+    (message "wisi-prj-cache-dominating used without 
wisi-prj-find-dominating-parse in project-find-functions"))
+
+  (setq dom-file (if dom-file (expand-file-name dom-file) (buffer-file-name)))
+  (setq prj-file (expand-file-name prj-file))
+  (add-to-list 'wisi-prj--dominating (file-name-nondirectory dom-file))
+  (add-to-list 'wisi-prj--dominating-alist (cons dom-file prj-file))
+  (add-to-list 'wisi-prj--default (cons prj-file default-prj))
+  nil)
+
+;;;###autoload
+(defun wisi-prj-find-dominating-parse (dir)
+  "For `project-find-functions'; parse, select, and return the project
+file matching `wisi-prj--dominating'."
+  (let ((prj-file (wisi-prj--find-dominating-file dir)))
+    (when prj-file
+      (let ((prj (wisi-prj-parse-file
+                 :prj-file prj-file
+                 :init-prj (cdr (assoc-string prj-file wisi-prj--default))
+                 :cache nil)))
+       (wisi-prj-select prj)
+       prj))))
+
+;;;###autoload
+(defun wisi-prj-dtrt-parse-file (prj-file default-prj dominating-file 
&optional dir)
+  "Depending on wisi-prj function in `project-find-functions',
+Do The Right Thing to make PRJ-FILE active and selected; return the project."
+  (cond
+   ((memq #'wisi-prj-find-dominating-parse project-find-functions)
+    (wisi-prj-set-dominating prj-file default-prj dominating-file))
+
+   ((memq #'wisi-prj-find-dominating-cached project-find-functions)
+    (wisi-prj-cache-dominating prj-file default-prj dominating-file))
+
+   ((memq #'wisi-prj-current-cached project-find-functions)
+    (wisi-prj-select-cache prj-file default-prj dominating-file))
+
+   ((memq #'wisi-prj-current-parse project-find-functions)
+    (wisi-prj-select-file prj-file default-prj dominating-file))
+
+   (t
+    (user-error "No wisi-prj function in project-find-functions"))
+   )
+  (project-current nil (or dir default-directory)))
+
+;;;###autoload
+(defun wisi-prj-find-function-set-p ()
+  "Return non-nil if a wisi-prj function is present in 
`project-find-functions'."
+  (or (memq #'wisi-prj-find-dominating-parse project-find-functions)
+      (memq #'wisi-prj-find-dominating-cached project-find-functions)
+      (memq #'wisi-prj-current-cached project-find-functions)
+      (memq #'wisi-prj-current-parse project-find-functions)))
+
+;;;; project menu
+
+(defun wisi-prj--menu-compute ()
+  "Return an easy-menu menu for `wisi-prj-menu--install'.
+Menu displays cached wisi projects."
+  (let (menu)
+    (dolist (item wisi-prj--cache)
+      (push
+       (vector
+       (concat (wisi-prj-name (cdr item))
+               (when (equal (car item) wisi-prj--current-file) "  *")) ;; 
current project
+       `(lambda () (interactive)
+          (when wisi-prj--current-file
+            (wisi-prj-deselect (cdr (assoc wisi-prj--current-file 
wisi-prj--cache))))
+          (setq wisi-prj--current-file ,(car item))
+          (wisi-prj-select ,(cdr item)))
+       t)
+       menu)
+      )
+    (nreverse menu)))
+
+(defun wisi-prj-menu-install ()
+  "Install the project menu if appropriate, to display cached wisi projects."
+  (when
+      (or (memq #'wisi-prj-find-dominating-cached project-find-functions)
+         (memq #'wisi-prj-current-cached project-find-functions))
+
+    (let ((menu (wisi-prj--menu-compute)))
+      (if menu
+         (define-key-after
+           global-map
+           [menu-bar wisi-prj-select]
+           (easy-menu-binding
+            (easy-menu-create-menu
+             "Wisi Prj Select";; EDE uses "Project" menu
+             menu))
+           (lookup-key global-map [menu-bar tools]))
+       ;; delete empty menu
+         (define-key-after
+           global-map
+           [menu-bar wisi-prj-select]
+           nil
+           (lookup-key global-map [menu-bar tools]))
+       ))
+    ))
+
+(add-hook 'menu-bar-update-hook 'wisi-prj-menu-install)
+
+(defun wisi-prj-completion-table ()
+  "Return list of names of cached projects."
+  (mapcar (lambda (item) (wisi-prj-name (cdr item))) wisi-prj--cache))
+
+(defun wisi-prj-delete (name)
+  "Delete project NAME (default prompt) from the cached projects."
+  (interactive (list (completing-read "project name: " 
(wisi-prj-completion-table))))
+  (let (pair)
+    (dolist (item wisi-prj--cache)
+      (if (string= name (wisi-prj-name (cdr item)))
+         (setq pair item)))
+
+    (setq wisi-prj--cache (delete pair wisi-prj--cache))
+
+    (setq wisi-prj--dominating-alist
+         (cl-delete-if (lambda (item)
+                         (string= (car pair) (cdr item)))
+                       wisi-prj--dominating-alist))
+    ))
+
+(provide 'wisi-prj)
+;; end wisi-prj.el
diff --git a/wisi-process-parse.el b/wisi-process-parse.el
index 5fb025d..46abc92 100644
--- a/wisi-process-parse.el
+++ b/wisi-process-parse.el
@@ -1,6 +1,6 @@
 ;;; wisi-process-parse.el --- interface to external parse program
 ;;
-;; Copyright (C) 2014, 2017 - 2019 Free Software Foundation, Inc.
+;; Copyright (C) 2014, 2017 - 2020 Free Software Foundation, Inc.
 ;;
 ;; Author: Stephen Leake <stephen_leake@member.fsf.org>
 ;;
@@ -31,10 +31,10 @@
   is no response from the parser after waiting this amount (in
   seconds)."
   :type 'float
-  :safe 'floatp)
+  :safe 'numberp)
 (make-variable-buffer-local 'wisi-process-time-out)
 
-(defconst wisi-process-parse-protocol-version "4"
+(defconst wisi-process-parse-protocol-version "5"
   "Defines data exchanged between this package and the background process.
 Must match emacs_wisi_common_parse.ads Protocol_Version.")
 
@@ -83,7 +83,8 @@ Otherwise add PARSER to ‘wisi-process--alist’, return it."
       (let ((exec-file (locate-file (wisi-process--parser-exec-file parser) 
exec-path '("" ".exe"))))
 
        (unless exec-file
-         (error "%s not found on `exec-path'" (wisi-process--parser-exec-file 
parser)))
+         (error "%s not found on `exec-path'; run 'build.sh' in the ELPA 
package."
+                (wisi-process--parser-exec-file parser)))
 
        (push (cons (wisi-process--parser-label parser) parser) 
wisi-process--alist)
 
@@ -225,7 +226,7 @@ PARSE-END, wait for command to complete. PARSER will 
respond with
 one or more Edit messages."
   ;; Must match "refactor" command arguments read by
   ;; emacs_wisi_common_parse.adb Get_Refactor_Params.
-  (let* ((cmd (format "refactor %d \"%s\" %d %d %d %d %d %d %d %d %d %d %d"
+  (let* ((cmd (format "refactor %d \"%s\" %d %d %d %d %d %d %d %d %d %d %d %d"
                      refactor-action
                      (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
                      (position-bytes parse-begin)
@@ -234,6 +235,7 @@ one or more Edit messages."
                      parse-begin ;; char_pos
                      (line-number-at-pos parse-begin)
                      (line-number-at-pos parse-end)
+                     (save-excursion (goto-char parse-begin) 
(back-to-indentation) (current-column));; indent-begin
                      (if (> wisi-debug 0) 1 0) ;; debug-mode
                      (1- wisi-debug) ;; trace_parse
                      wisi-trace-action
@@ -396,7 +398,7 @@ complete."
       (cl-do ((i 1 (1+ i))) ((= i (length sexp)))
        (push
         (make-wisi--parse-error-repair
-         :pos (aref (aref sexp i) 0)
+         :pos (copy-marker (aref (aref sexp i) 0))
          :inserted (mapcar (lambda (id) (aref token-table id)) (aref (aref 
sexp i) 1))
          :deleted  (mapcar (lambda (id) (aref token-table id)) (aref (aref 
sexp i) 2))
          :deleted-region (aref (aref sexp i) 3))
@@ -406,7 +408,7 @@ complete."
 (defun wisi-process-parse--End (parser sexp)
   ;; sexp is [End pos]
   ;; see ‘wisi-process-parse--execute’
-  (setf (wisi-process--parser-end-pos parser) (aref sexp 1)))
+  (setf (wisi-process--parser-end-pos parser) (1+ (aref sexp 1))))
 
 (defun wisi-process-parse--Edit (parser sexp)
   ;; sexp is [Edit begin end text]
@@ -703,7 +705,7 @@ complete."
 
              (when (and (= (point-max) need-more)
                         (> (wisi-process--parser-total-wait-time parser) 
wisi-process-time-out))
-               (error "wisi-process-parse not getting more text (or bad syntax 
in process output)"))
+               (error "wisi-process-parse timing out; increase 
`wisi-process-time-out'? (or bad syntax in process output)"))
 
              (setq need-more nil))
            );; while not done
diff --git a/wisi-run-indent-test.el b/wisi-run-indent-test.el
index fcf0b33..269b3d9 100644
--- a/wisi-run-indent-test.el
+++ b/wisi-run-indent-test.el
@@ -1,6 +1,6 @@
 ;;; wisi-run-indent-test.el --- utils for automating indentation and casing 
tests
 ;;
-;; Copyright (C) 2018 - 2019  Free Software Foundation, Inc.
+;; Copyright (C) 2018 - 2020  Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -18,6 +18,7 @@
 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 (require 'wisi-tests)
+(require 'wisi-prj)
 
 ;; user can set these to t in an EMACSCMD
 (defvar skip-cmds nil)
@@ -170,6 +171,11 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
   (setq indent-tabs-mode nil)
   (setq jit-lock-context-time 0.0);; for test-face
 
+  ;; Test files use wisi-prj-select-cached to parse and select a project file.
+  (setq project-find-functions (list #'wisi-prj-current-cached))
+  (setq xref-backend-functions (list #'wisi-prj-xref-backend))
+
+
   (let ((error-count 0)
        (test-buffer (current-buffer))
        cmd-line
@@ -185,6 +191,11 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
     ;;    of the previous EMACSCMD, and the test fails if they don't
     ;;    match.
     ;;
+    ;; EMACSRESULT_START:<first list element>
+    ;; EMACSRESULT_ADD:  <list element>
+    ;; EMACSRESULT_FINISH:
+    ;;    build a list, compare it to the result of the previous EMACSCMD.
+    ;;
     ;; EMACS_SKIP_UNLESS: <form>
     ;;   skip entire test if form evals nil
     ;;
@@ -223,7 +234,6 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
        (looking-at ".*$")
        (setq expected-result (save-excursion (end-of-line 1) (eval (car 
(read-from-string (match-string 0))))))
        (unless (equal expected-result last-result)
-         (when debug-on-error (debug))
          (setq error-count (1+ error-count))
          (message
           (concat
@@ -234,6 +244,43 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
                    expected-result)
            ))))
 
+       ((string= (match-string 1) "RESULT_START")
+       (looking-at ".*$")
+       (setq expected-result (list (save-excursion (end-of-line 1) (eval (car 
(read-from-string (match-string 0))))))))
+
+       ((string= (match-string 1) "RESULT_ADD")
+       (looking-at ".*$")
+       (let ((val (save-excursion (end-of-line 1)
+                                  (eval (car (read-from-string (match-string 
0)))))))
+         (when val
+           (setq expected-result (append expected-result (list val))))))
+
+       ((string= (match-string 1) "RESULT_FINISH")
+       (unless (equal (length expected-result) (length last-result))
+         (setq error-count (1+ error-count))
+         (message
+          (concat
+           (format "error: %s:%d:\n" (buffer-file-name) (line-number-at-pos))
+           (format "Length of result of '%s' does not match.\nGot    
'%s',\nexpect '%s'"
+                   last-cmd
+                   (length last-result)
+                   (length expected-result)))))
+
+       (let ((i 0))
+         (while (< i (length expected-result))
+           (unless (equal (nth i expected-result) (nth i last-result))
+             (setq error-count (1+ error-count))
+             (message
+              (concat
+               (format "error: %s:%d:\n" (buffer-file-name) 
(line-number-at-pos))
+               (format "Nth (%d) result of '%s' does not match.\nGot    
'%s',\nexpect '%s'"
+                       i
+                       last-cmd
+                       (nth i last-result)
+                       (nth i expected-result))
+               )))
+           (setq i (1+ i)))))
+
        ((string= (match-string 1) "_SKIP_UNLESS")
        (looking-at ".*$")
        (unless (eval (car (read-from-string (match-string 0))))
@@ -261,7 +308,7 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
        (buffer-file-name) (line-number-at-pos (point)) error-count))
     )
 
-  (when (not skip-reindent-test)
+  (unless skip-reindent-test
     ;; Reindent the buffer
     (message "indenting")
 
@@ -278,11 +325,18 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
     ;; files must be saved without any.
     (delete-trailing-whitespace)
     )
+
+  (when (and wisi-auto-case (not skip-recase-test))
+    (message "casing")
+    (wisi-case-adjust-buffer))
   )
 
 (defun run-test (file-name)
   "Run an indentation and casing test on FILE-NAME."
   (interactive "f")
+
+  (package-initialize) ;; for uniquify-files
+
   ;; we'd like to run emacs from a makefile as:
   ;;
   ;; emacs -Q --batch -l runtest.el -f run-test-here <filename>
@@ -304,14 +358,11 @@ Each item is a list (ACTION PARSE-BEGIN PARSE-END 
EDIT-BEGIN)")
   ;; that rely on font-lock do explicitly.
   (setq font-lock-support-mode nil)
 
+  (setq xref-prompt-for-identifier nil)
+
   (let ((dir default-directory))
     (find-file file-name) ;; sets default-directory
 
-    (when (eq major-mode 'fundamental-mode)
-      ;; Running a grammar in test/wisi
-      (add-to-list 'load-path (expand-file-name "."))
-      (wisi-tests-setup (file-name-sans-extension (file-name-nondirectory 
file-name))))
-
     (run-test-here)
 
     (unless skip-write
diff --git a/wisi-skel.el b/wisi-skel.el
new file mode 100644
index 0000000..96693fd
--- /dev/null
+++ b/wisi-skel.el
@@ -0,0 +1,154 @@
+;;; wisi-skel.el --- Extensions skeleton  -*- lexical-binding:t -*-
+
+;; Copyright (C) 1987, 1993, 1994, 1996-2019  Free Software Foundation, Inc.
+
+;; Authors: Stephen Leake <stephen_leake@stephe-leake.org>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Design:
+;;
+;; The primary user command is `wisi-skel-expand', which inserts the
+;; skeleton associated with the previous word (possibly skipping a
+;; name).
+;;
+
+(defvar-local wisi-skel-token-alist nil
+  "Alist of (STRING . ELEMENT), used by `wisi-skel-expand'.
+STRING must be a symbol in the current syntax, and is normally
+the first language keyword in the skeleton.
+
+ELEMENT may be:
+- a skeleton, which is inserted
+- an alist of (STRING . SKELETON). User is prompted with `completing-read',
+  selected skeleton is inserted.")
+
+(defun wisi-skel-build-prompt (alist count)
+  "Build a prompt from the keys of the ALIST.
+The prompt consists of the first COUNT keys from the alist, separated by `|', 
with
+trailing `...' if there are more keys."
+  (if (>= count (length alist))
+      (concat (mapconcat 'car alist " | ") " : ")
+    (let ((alist-1 (butlast alist (- (length alist) count))))
+      (concat (mapconcat 'car alist-1 " | ") " | ... : "))
+  ))
+
+(defvar wisi-skel-test-input nil
+  "Override prompt for input from wisi-skel-token-alist, for unit testing."
+  ;; see test/ada_skel.adb
+  )
+
+(defun wisi-skel-expand (&optional name)
+  "Expand the token or placeholder before point to a skeleton.
+Tokens are defined by `wisi-skel-token-alist'; they must have
+symbol syntax.  A placeholder is a token enclosed in generic
+comment delimiters.  If the symbol before point is not in
+`wisi-skel-token-alist', assume it is a name, and use the symbol
+before that as the token."
+  (interactive "*")
+
+  ;; Skip trailing space, newline, and placeholder delimiter.
+  ;; Standard comment end included for languages where that is newline.
+  (skip-syntax-backward " !>")
+
+  (let* ((wisi-inhibit-parse t) ;; don't parse until skeleton is fully inserted
+        (end (point))
+        ;; Include punctuation here, to handle a dotted name (ie Ada.Text_IO)
+        (token (progn (skip-syntax-backward "w_.")
+                      (downcase (buffer-substring-no-properties (point) end))))
+        (skel (assoc-string token wisi-skel-token-alist))
+        (handled nil))
+
+    (if skel
+       (progn
+         (when (listp (cdr skel))
+           (let* ((alist (cdr skel))
+                  (prompt (wisi-skel-build-prompt alist 4)))
+             (setq skel (assoc-string
+                         (or wisi-skel-test-input (completing-read prompt 
alist))
+                         alist))
+             ))
+
+         ;; delete placeholder delimiters around token, token, and
+         ;; name. point is currently before token.
+         (skip-syntax-backward "!")
+         (delete-region
+          (point)
+          (progn
+            (skip-syntax-forward "!w_")
+            (when name
+              (skip-syntax-forward " ")
+              (skip-syntax-forward "w_."))
+            (point)))
+         (funcall (cdr skel) name)
+         (setq handled t))
+
+      ;; word in point .. end is not a token; assume it is a name
+      (when (not name)
+       ;; avoid infinite recursion
+
+       (when wisi-auto-case
+         ;; Adjust case now, because skeleton insert won't.
+         ;;
+         ;; We didn't do it above, because we don't want to adjust case
+         ;; on tokens and placeholders.
+         (save-excursion (wisi-case-adjust-region (point) end)))
+
+       (wisi-skel-expand (buffer-substring-no-properties (point) end))
+       (setq handled t)))
+
+    (when (not handled)
+      (user-error "'%s' is not a skeleton token" name))
+    ))
+
+;;;###autoload
+(defun wisi-skel-hippie-try (old)
+  "For `hippie-expand-try-functions-list'."
+  (if old
+      ;; hippie is asking us to try the "next" completion; we don't have one
+      nil
+    (let ((pos (point))
+         (undo-len (if (eq 't pending-undo-list)
+                       0
+                     (length pending-undo-list))))
+      (undo-boundary)
+      (condition-case nil
+         (progn
+           (wisi-skel-expand)
+           t)
+       (error
+        ;; undo hook action if any
+        (unless (or (eq 't pending-undo-list)
+                    (= undo-len (length pending-undo-list)))
+          (undo))
+
+        ;; undo motion
+        (goto-char pos)
+        nil)))))
+
+(defun wisi-skel-next-placeholder ()
+  "Move point to after next placeholder."
+  (interactive)
+  (skip-syntax-forward "^!")
+  (skip-syntax-forward "w_!"))
+
+(defun wisi-skel-prev-placeholder ()
+  "Move point to after previous placeholder."
+  (interactive)
+  (skip-syntax-backward "^!"))
+
+(provide 'wisi-skel)
+;;; wisi-skel.el ends here
diff --git a/wisi.adb b/wisi.adb
index 1ba7a4f..227c45c 100644
--- a/wisi.adb
+++ b/wisi.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2020 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -95,7 +95,7 @@ package body Wisi is
       Indent       : in out Indent_Type)
    with Pre => Delta_Indent.Label = Anchored
    is begin
-      --  [2] wisi-elisp-parse--apply-anchored; add Delta_Indent to Indent
+      --  Add Delta_Indent to Indent
 
       case Indent.Label is
       when Not_Set =>
@@ -130,7 +130,7 @@ package body Wisi is
 
    procedure Indent_Apply_Int (Indent : in out Indent_Type; Offset : in 
Integer)
    is begin
-      --  [2] wisi-elisp-parse--apply-int; add an Int indent to Indent
+      --  Add an Int indent to Indent
       case Indent.Label is
       when Not_Set =>
          Indent := (Int, Offset);
@@ -255,8 +255,6 @@ package body Wisi is
       Paren_Char_Pos : Buffer_Pos       := Invalid_Buffer_Pos;
       Text_Begin_Pos : Buffer_Pos       := Invalid_Buffer_Pos;
    begin
-      --  [1] wisi-elisp-parse--paren-in-anchor-line. That uses elisp 
syntax-ppss; here
-      --  we search Terminals.
       loop
          declare
             Tok : Augmented_Token renames Data.Terminals (I);
@@ -332,8 +330,8 @@ package body Wisi is
    begin
       if Cache.Face.Set then
          Append (Line, Face_Property_Code);
-         Append (Line, Buffer_Pos'Image (Cache.Region.First));
-         Append (Line, Buffer_Pos'Image (Cache.Region.Last));
+         Append (Line, Buffer_Pos'Image (Cache.Char_Region.First));
+         Append (Line, Buffer_Pos'Image (Cache.Char_Region.Last));
          Append (Line, Integer'Image (Cache.Face.Item));
          Append (Line, ']');
          Ada.Text_IO.Put_Line (To_String (Line));
@@ -644,7 +642,7 @@ package body Wisi is
       Data.Terminals.Clear;
       Data.Leading_Non_Grammar.Clear;
       --  Data.Line_Begin_Pos  set in Initialize, overwritten in 
Lexer_To_Augmented
-      --  Data.Line_Begin_Token  ""
+      --  Data.Line_Begin_Token set in WisiToken.Parse.Next_Grammar_Token.
 
       for S of Data.Line_Paren_State loop
          S := 0;
@@ -1102,7 +1100,7 @@ package body Wisi is
               (File_Name => Data.Lexer.File_Name,
                Line      => Name_Token.Line,
                Column    => Name_Token.Column,
-               Message   => "wisi-name-action: name set twice.");
+               Message   => Trimmed_Image (Tree.Production_ID (Nonterm)) & ": 
wisi-name-action: name set twice.");
          else
             Data.Name_Caches.Insert (Name_Token.Char_Region);
          end if;
@@ -1116,7 +1114,6 @@ package body Wisi is
       Tokens  : in     Syntax_Trees.Valid_Node_Index_Array;
       Params  : in     Motion_Param_Array)
    is
-      --  [2] wisi-motion-action
       use Navigate_Cache_Trees;
 
       Start          : Nil_Buffer_Pos    := (Set => False);
@@ -1257,7 +1254,7 @@ package body Wisi is
                               Suf_Cache : Face_Cache_Type renames 
Data.Face_Caches (Suffix_Cur);
                            begin
                               if Suffix = Suf_Cache.Class and
-                                Inside (Suf_Cache.Region.First, 
Token.Char_Region)
+                                Inside (Suf_Cache.Char_Region.First, 
Token.Char_Region)
                               then
                                  Suf_Cache.Face := (True, Param.Suffix_Face);
                               end if;
@@ -1284,8 +1281,6 @@ package body Wisi is
       Params  : in     Face_Apply_Param_Array)
    is
       pragma Unreferenced (Nonterm);
-
-      --  [2] wisi-face-apply-list-action
       use Face_Cache_Trees;
 
       Iter      : constant Iterator := Data.Face_Caches.Iterate;
@@ -1299,7 +1294,7 @@ package body Wisi is
                Cache_Cur := Find_In_Range (Iter, Ascending, 
Token.Char_Region.First, Token.Char_Region.Last);
                loop
                   exit when not Has_Element (Cache_Cur) or else
-                    Data.Face_Caches (Cache_Cur).Region.First > 
Token.Char_Region.Last;
+                    Data.Face_Caches (Cache_Cur).Char_Region.First > 
Token.Char_Region.Last;
                   declare
                      Cache : Face_Cache_Type renames Data.Face_Caches 
(Cache_Cur);
                   begin
@@ -1342,19 +1337,19 @@ package body Wisi is
                   declare
                      Cache : Face_Cache_Type renames Data.Face_Caches 
(Cache_Cur);
                      Other_Cur : Cursor := Find_In_Range
-                       (Iter, Ascending, Cache.Region.Last + 1, 
Token.Char_Region.Last);
+                       (Iter, Ascending, Cache.Char_Region.Last + 1, 
Token.Char_Region.Last);
                      Temp : Cursor;
                   begin
                      loop
                         exit when not Has_Element (Other_Cur) or else
-                          Data.Face_Caches (Other_Cur).Region.First > 
Token.Char_Region.Last;
+                          Data.Face_Caches (Other_Cur).Char_Region.First > 
Token.Char_Region.Last;
                         Temp := Other_Cur;
                         Other_Cur := Next (Iter, Other_Cur);
                         Delete (Data.Face_Caches, Temp);
                      end loop;
 
-                     Cache.Class       := Param.Class;
-                     Cache.Region.Last := Token.Char_Region.Last;
+                     Cache.Class            := Param.Class;
+                     Cache.Char_Region.Last := Token.Char_Region.Last;
                   end;
                else
                   Data.Face_Caches.Insert ((Token.Char_Region, Param.Class, 
(Set => False)));
@@ -1372,8 +1367,6 @@ package body Wisi is
       Params  : in     Face_Remove_Param_Array)
    is
       pragma Unreferenced (Nonterm);
-
-      --  [2] wisi-face-remove-action
       use Face_Cache_Trees;
 
       Iter      : constant Iterator := Data.Face_Caches.Iterate;
@@ -1388,7 +1381,7 @@ package body Wisi is
                Cache_Cur := Find_In_Range (Iter, Ascending, 
Token.Char_Region.First, Token.Char_Region.Last);
                loop
                   exit when not Has_Element (Cache_Cur) or else
-                    Data.Face_Caches (Cache_Cur).Region.First > 
Token.Char_Region.Last;
+                    Data.Face_Caches (Cache_Cur).Char_Region.First > 
Token.Char_Region.Last;
                   Temp := Cache_Cur;
                   Cache_Cur := Next (Iter, Cache_Cur);
                   Delete (Data.Face_Caches, Temp);
@@ -1459,7 +1452,6 @@ package body Wisi is
          Ada.Text_IO.Put_Line (";; indent_action_0: " & Tree.Image (Nonterm, 
Data.Descriptor.all));
       end if;
 
-      --  [2] wisi-indent-action
       for I in Tokens'Range loop
          if Tree.Byte_Region (Tokens (I)) /= Null_Buffer_Region and
            I in Params'Range -- in some translated EBNF, not every token has 
an indent param
@@ -1520,7 +1512,6 @@ package body Wisi is
    is
       use all type Syntax_Trees.Node_Label;
    begin
-      --  [2] wisi-indent-action*
       for I in Tokens'First .. N loop
          if Tree.Label (Tokens (I)) /= Virtual_Terminal and then
            Get_Aug_Token (Data, Tree, Tokens (I)).First
@@ -1931,7 +1922,7 @@ package body Wisi is
       Accumulate  : in     Boolean)
      return Delta_Type
    is
-      --  [2] wisi-elisp-parse--anchored-2; return an anchored delta
+      --  Return an anchored delta
       use Anchor_ID_Vectors;
       --  We can't use a Reference here, because the Element in reference
       --  types is constrained (as are all allocated objects of access
@@ -1987,7 +1978,7 @@ package body Wisi is
    is
       Indenting_Token : constant Aug_Token_Ref := Get_Aug_Token (Data, Tree, 
Tree_Indenting);
    begin
-      --  [2] wisi-elisp-parse--indent-compute-delta, which evals 
wisi-anchored*, wisi-hanging*.
+      --  Evaluate wisi-anchored*, wisi-hanging*.
       case Param.Label is
       when Simple =>
          case Param.Param.Label is
@@ -2010,7 +2001,7 @@ package body Wisi is
                begin
                   case Anchored_Label'(Param.Param.Label) is
                   when Anchored_0 =>
-                     --  [2] wisi-anchored, wisi-anchored-1
+                     --  [2] wisi-anchored
                      return Indent_Anchored_2
                        (Data,
                         Anchor_Line => Anchor_Token.Line,
@@ -2115,10 +2106,55 @@ package body Wisi is
          if Data.Indent_Comment_Col_0 then
             declare
                use all type Ada.Text_IO.Count;
-               Indent : Boolean := True;
+
+               function Containing_Token return Base_Token_Index
+               is
+                  --  Return token index of terminal containing non_grammer on 
Line;
+                  --  Invalid_Token_Index if none.
+                  I : Line_Number_Type := Line;
+                  J : Base_Token_Index;
+               begin
+                  if Line < Data.Line_Begin_Token.First_Index then
+                     --  Line is before first grammar token; 
Leading_Non_Grammar checked
+                     --  below.
+                     return Invalid_Token_Index;
+                  end if;
+
+                  loop
+                     exit when Data.Line_Begin_Token.all (I) /= 
Augmented_Token_Arrays.No_Index;
+                     --  No_Index means Line is in a multi-line token, which 
could be a block comment.
+                     I := I - 1;
+                  end loop;
+
+                  J := Data.Line_Begin_Token.all (I);
+                  if Line in Data.Terminals (J).First_Trailing_Comment_Line ..
+                    Data.Terminals (J).Last_Trailing_Comment_Line
+                  then
+                     return J;
+                  else
+                     return Invalid_Token_Index;
+                  end if;
+               end Containing_Token;
+
+               Indent     : Boolean                   := True;
+               Containing : constant Base_Token_Index := Containing_Token;
             begin
-               if Data.Line_Begin_Token.all (Line - 1) /= 
Augmented_Token_Arrays.No_Index then
-                  for Tok of Data.Terminals (Data.Line_Begin_Token.all (Line - 
1)).Non_Grammar loop
+               if Line < Data.Line_Begin_Token.First_Index then
+                  --  Line is before the first grammar token. We may be doing 
a partial
+                  --  parse where the initial indent is non-zero, so we still 
have to
+                  --  check for column 0.
+                  for Tok of Data.Leading_Non_Grammar loop
+                     if Tok.Line = Line and then
+                       Tok.ID in Data.First_Comment_ID .. Data.Last_Comment_ID 
and then
+                       Tok.Column = 0
+                     then
+                        Indent := False;
+                        exit;
+                     end if;
+                  end loop;
+
+               elsif Containing /= Invalid_Token_Index then
+                  for Tok of Data.Terminals (Containing).Non_Grammar loop
                      if Tok.Line = Line and then
                        Tok.ID in Data.First_Comment_ID .. Data.Last_Comment_ID 
and then
                        Tok.Column = 0
diff --git a/wisi.ads b/wisi.ads
index d240f0a..f871888 100644
--- a/wisi.ads
+++ b/wisi.ads
@@ -10,7 +10,7 @@
 --
 --  [3] wisi-process-parse.el - defines elisp/process API
 --
---  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2020 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -186,21 +186,17 @@ package Wisi is
    ----------
    --  Indent
    --
-   --  elisp indent functions are represented by the Indent_Param type,
-   --  not Ada functions. This is to get the execution time right; in
-   --  elisp, the array of parameters to wisi-indent-action is not
-   --  evaluated when wisi-indent-action is called; each parameter is
-   --  evaluated by wisi-elisp-parse--indent-compute-delta.
+   --  Indent functions are represented by the Indent_Param type.
 
    type Simple_Indent_Param_Label is -- not hanging
      (None,
       Int,
-      Anchored_0, -- wisi-anchored
-      Anchored_1, -- wisi-anchored%
-      Anchored_2, -- wisi-anchored%-
-      Anchored_3, -- wisi-anchored*
-      Anchored_4, -- wisi-anchored*-
-      Language    -- language-specific function
+      Anchored_0, -- [2] wisi-anchored
+      Anchored_1, -- [2] wisi-anchored%
+      Anchored_2, -- [2] wisi-anchored%-
+      Anchored_3, -- [2] wisi-anchored*
+      Anchored_4, -- [2] wisi-anchored*-
+      Language    -- [2] language-specific function
      );
    subtype Anchored_Label is Simple_Indent_Param_Label range Anchored_0 .. 
Anchored_4;
 
@@ -250,10 +246,10 @@ package Wisi is
 
    type Indent_Param_Label is
      (Simple,
-      Hanging_0, -- wisi-hanging
-      Hanging_1, -- wisi-hanging-
-      Hanging_2, -- wisi-hanging%
-      Hanging_3  -- wisi-hanging%-
+      Hanging_0, -- [2] wisi-hanging
+      Hanging_1, -- [2] wisi-hanging-
+      Hanging_2, -- [2] wisi-hanging%
+      Hanging_3  -- [2] wisi-hanging%-
      );
    subtype Hanging_Label is Indent_Param_Label range Hanging_0 .. Hanging_3;
 
@@ -464,15 +460,15 @@ private
    Nil : constant Nil_Buffer_Pos := (Set => False);
 
    type Navigate_Cache_Type is record
-      Pos            : WisiToken.Buffer_Pos; -- implicit in wisi-cache
-      Statement_ID   : WisiToken.Token_ID;   -- wisi-cache-nonterm
-      ID             : WisiToken.Token_ID;   -- wisi-cache-token
-      Length         : Natural;              -- wisi-cache-last
-      Class          : Navigate_Class_Type;  -- wisi-cache-class
-      Containing_Pos : Nil_Buffer_Pos;       -- wisi-cache-containing
-      Prev_Pos       : Nil_Buffer_Pos;       -- wisi-cache-prev
-      Next_Pos       : Nil_Buffer_Pos;       -- wisi-cache-next
-      End_Pos        : Nil_Buffer_Pos;       -- wisi-cache-end
+      Pos            : WisiToken.Buffer_Pos; -- implicit in [1] wisi-cache
+      Statement_ID   : WisiToken.Token_ID;   -- [1] wisi-cache-nonterm
+      ID             : WisiToken.Token_ID;   -- [1] wisi-cache-token
+      Length         : Natural;              -- [1] wisi-cache-last
+      Class          : Navigate_Class_Type;  -- [1] wisi-cache-class
+      Containing_Pos : Nil_Buffer_Pos;       -- [1] wisi-cache-containing
+      Prev_Pos       : Nil_Buffer_Pos;       -- [1] wisi-cache-prev
+      Next_Pos       : Nil_Buffer_Pos;       -- [1] wisi-cache-next
+      End_Pos        : Nil_Buffer_Pos;       -- [1] wisi-cache-end
    end record;
 
    function Key (Cache : in Navigate_Cache_Type) return WisiToken.Buffer_Pos 
is (Cache.Pos);
@@ -500,12 +496,12 @@ private
    end record;
 
    type Face_Cache_Type is record
-      Region : WisiToken.Buffer_Region;
-      Class  : Face_Class_Type;
-      Face   : Nil_Integer;     -- not set, or index into *-process-faces-names
+      Char_Region : WisiToken.Buffer_Region;
+      Class       : Face_Class_Type;
+      Face        : Nil_Integer; -- not set, or index into 
*-process-faces-names
    end record;
 
-   function Key (Cache : in Face_Cache_Type) return WisiToken.Buffer_Pos is 
(Cache.Region.First);
+   function Key (Cache : in Face_Cache_Type) return WisiToken.Buffer_Pos is 
(Cache.Char_Region.First);
 
    package Face_Cache_Trees is new SAL.Gen_Unbounded_Definite_Red_Black_Trees 
(Face_Cache_Type, WisiToken.Buffer_Pos);
 
@@ -514,8 +510,7 @@ private
    package Anchor_ID_Vectors is new Ada.Containers.Vectors (Natural, Positive);
 
    type Indent_Type (Label : Indent_Label := Not_Set) is record
-      --  [2] wisi-elisp-parse--indent elements. Indent values may be
-      --  negative while indents are being computed.
+      --  Indent values may be negative while indents are being computed.
       case Label is
       when Not_Set =>
          null;
@@ -641,7 +636,6 @@ private
 
    type Delta_Type (Label : Delta_Labels := Simple) is
    record
-      --  Matches DELTA input to wisi--indent-token-1
       case Label is
       when Simple =>
          Simple_Delta : Simple_Delta_Type;
@@ -704,7 +698,6 @@ private
       Offset      : in     Integer;
       Accumulate  : in     Boolean)
      return Delta_Type;
-   --  [2] wisi-elisp-parse--anchored-2
 
    function Indent_Compute_Delta
      (Data              : in out Parse_Data_Type'Class;
@@ -720,11 +713,11 @@ private
       Indenting_Token   : in     Augmented_Token'Class;
       Delta_Indent      : in     Delta_Type;
       Indenting_Comment : in     Boolean);
-   --  [2] wisi-elisp-parse--indent-token-1. Sets Data.Indents, so caller
-   --  may not be in a renames for a Data.Indents element.
+   --  Sets Data.Indents, so caller may not be in a renames for a
+   --  Data.Indents element.
 
    --  Visible for language-specific children. Must match list in
-   --  wisi-process-parse.el wisi-process-parse--execute.
+   --  [3] wisi-process-parse--execute.
    Navigate_Cache_Code  : constant String := "1";
    Face_Property_Code   : constant String := "2";
    Indent_Code          : constant String := "3";
diff --git a/wisi.el b/wisi.el
index a7a2fbf..6cdf2b5 100644
--- a/wisi.el
+++ b/wisi.el
@@ -1,13 +1,13 @@
 ;;; wisi.el --- Utilities for implementing an indentation/navigation engine 
using a generalized LALR parser -*- lexical-binding:t -*-
 ;;
-;; Copyright (C) 2012 - 2019  Free Software Foundation, Inc.
+;; Copyright (C) 2012 - 2020  Free Software Foundation, Inc.
 ;;
 ;; Author: Stephen Leake <stephen_leake@stephe-leake.org>
 ;; Maintainer: Stephen Leake <stephen_leake@stephe-leake.org>
 ;; Keywords: parser
 ;;  indentation
 ;;  navigation
-;; Version: 2.2.1
+;; Version: 3.0.1
 ;; package-requires: ((emacs "25.0") (seq "2.20"))
 ;; URL: http://stephe-leake.org/ada/wisitoken.html
 ;;
@@ -127,11 +127,6 @@ deeply nested statement, but worse in some situations."
   :group 'wisi
   :safe 'integerp)
 
-(defvar wisi-inhibit-parse nil
-  "When non-nil, don't run the parser.
-Language code can set this non-nil when syntax is known to be
-invalid temporarily, or when making lots of changes.")
-
 (defcustom wisi-disable-face nil
   "When non-nil, `wisi-setup' does not enable use of parser for font-lock.
 Useful when debugging parser or parser actions."
@@ -145,6 +140,11 @@ Useful when debugging parser or parser actions."
 (defvar wisi-error-buffer nil
   "Buffer for displaying syntax errors.")
 
+(defvar wisi-inhibit-parse nil
+  "When non-nil, don't run the parser.
+Language code can set this non-nil when syntax is known to be
+invalid temporarily, or when making lots of changes.")
+
 (defun wisi-safe-marker-pos (pos)
   "Return an integer buffer position from POS, an integer or marker"
   (cond
@@ -153,6 +153,61 @@ Useful when debugging parser or parser actions."
 
    (t pos)))
 
+;;;; misc
+
+(defun wisi-in-paren-p (&optional parse-result)
+  "Return t if point is inside a pair of parentheses.
+If PARSE-RESULT is non-nil, use it instead of calling `syntax-ppss'."
+  (> (nth 0 (or parse-result (syntax-ppss))) 0))
+
+(defun wisi-pos-in-paren-p (pos)
+  "Return t if POS is inside a pair of parentheses."
+  (save-excursion
+    (> (nth 0 (syntax-ppss pos)) 0)))
+
+(defun wisi-same-paren-depth-p (pos1 pos2)
+  "Return t if POS1 is at same parentheses depth as POS2."
+  (= (nth 0 (syntax-ppss pos1)) (nth 0 (syntax-ppss pos2))))
+
+(defun wisi-goto-open-paren (&optional offset parse-result)
+  "Move point to innermost opening paren surrounding current point, plus 
OFFSET.
+Throw error if not in paren.  If PARSE-RESULT is non-nil, use it
+instead of calling `syntax-ppss'."
+  (goto-char (+ (or offset 0) (nth 1 (or parse-result (syntax-ppss))))))
+
+(defun wisi-in-comment-p (&optional parse-result)
+  "Return t if inside a comment.
+If PARSE-RESULT is non-nil, use it instead of calling `syntax-ppss'."
+  (nth 4 (or parse-result (syntax-ppss))))
+
+(defun wisi-in-string-p (&optional parse-result)
+  "Return t if point is inside a string.
+If PARSE-RESULT is non-nil, use it instead of calling `syntax-ppss'."
+  (nth 3 (or parse-result (syntax-ppss))))
+
+(defun wisi-in-string-or-comment-p (&optional parse-result)
+  "Return t if inside a comment or string.
+If PARSE-RESULT is non-nil, use it instead of calling `syntax-ppss'."
+  (setq parse-result (or parse-result (syntax-ppss)))
+  (or (wisi-in-string-p parse-result) (wisi-in-comment-p parse-result)))
+
+(defun wisi-indent-newline-indent ()
+  "insert a newline, indent the old and new lines."
+  (interactive "*")
+  ;; point may be in the middle of a word, so insert newline first,
+  ;; then go back and indent.
+  (insert "\n")
+  (unless (and (wisi-partial-parse-p (line-beginning-position) 
(line-end-position))
+              (save-excursion (progn (forward-char -1)(looking-back 
"begin\\|else" (line-beginning-position)))))
+    ;; Partial parse may think 'begin' is just the start of a
+    ;; statement, when it's actually part of a larger declaration. So
+    ;; don't indent 'begin'. Similarly for 'else'; error recovery will
+    ;; probably insert 'if then' immediately before it
+    (forward-char -1)
+    (funcall indent-line-function)
+    (forward-char 1))
+  (funcall indent-line-function))
+
 ;;;; token info cache
 
 (defvar-local wisi-parse-failed nil
@@ -634,8 +689,10 @@ Used to ignore whitespace changes in before/after change 
hooks.")
       ;; else show all errors in a ’compilation’ buffer
       (setq wisi-error-buffer (get-buffer-create wisi-error-buffer-name))
 
-      (let ((lexer-errs (nreverse (cl-copy-seq (wisi-parser-lexer-errors 
wisi--parser))))
-           (parse-errs (nreverse (cl-copy-seq (wisi-parser-parse-errors 
wisi--parser))))
+      (let ((lexer-errs (sort (cl-copy-seq (wisi-parser-lexer-errors 
wisi--parser))
+                             (lambda (a b) (< (wisi--parse-error-pos a) 
(wisi--parse-error-pos b)))))
+           (parse-errs (sort (cl-copy-seq (wisi-parser-parse-errors 
wisi--parser))
+                             (lambda (a b) (< (wisi--parse-error-pos a) 
(wisi--parse-error-pos b)))))
            (dir default-directory))
        (with-current-buffer wisi-error-buffer
          (setq window-size-fixed nil)
@@ -690,9 +747,7 @@ Usefull if the parser appears to be hung."
   (interactive)
   (wisi-parse-kill wisi--parser)
   ;; also force re-parse
-  (dolist (parse-action '(face navigate indent))
-    (wisi-set-parse-try t parse-action)
-    (wisi-invalidate-cache parse-action (point-min)))
+  (wisi-reset-parser)
   )
 
 (defun wisi-partial-parse-p (begin end)
@@ -1029,6 +1084,18 @@ Return start cache."
       (wisi-goto-end-1 cache))
     ))
 
+(defun wisi-goto-containing-statement-start ()
+  "Move point to the start of the statement containing the current statement."
+  (interactive)
+  (wisi-validate-cache (point-min) (point-max) t 'navigate)
+  (let ((cache (or (wisi-get-cache (point))
+                  (wisi-backward-cache))))
+    (when cache
+      (setq cache (wisi-goto-start cache)))
+    (when cache
+      (setq cache (wisi-goto-containing cache nil)))
+    ))
+
 (defun wisi-next-statement-cache (cache)
   "Move point to CACHE-next, return cache; error if nil."
   (when (not (markerp (wisi-cache-next cache)))
@@ -1096,6 +1163,27 @@ the comment on the previous line."
          ))
       )))
 
+(defun wisi-indent-containing-statement ()
+  "Indent region given by `wisi-goto-containing-statement-start', 
`wisi-cache-end'."
+  (interactive)
+  (wisi-validate-cache (point-min) (point-max) t 'navigate)
+
+  (save-excursion
+    (let ((cache (or (wisi-get-cache (point))
+                    (wisi-backward-cache))))
+      (when cache
+       ;; can be nil if in header comment
+       (let ((start (progn
+                      (setq cache (wisi-goto-containing (wisi-goto-start 
cache)))
+                      (point)))
+             (end (if (wisi-cache-end cache)
+                        ;; nil when cache is statement-end
+                        (wisi-cache-end cache)
+                      (point))))
+         (indent-region start end)
+         ))
+      )))
+
 (defvar-local wisi-indent-calculate-functions nil
   "Functions to compute indentation special cases.
 Called with point at current indentation of a line; return
@@ -1125,8 +1213,10 @@ Called with BEGIN END.")
   (let ((col (current-column)))
     (while (and (not (eobp))
                (< (point) end))
-      (forward-line 1)
-      (indent-line-to col)
+      (if (= 1 (forward-line 1))
+         (indent-line-to col)
+       ;; on last line of buffer; terminate loop
+       (goto-char (point-max)))
       (when (bobp)
        ;; single line in buffer; terminate loop
        (goto-char (point-max))))))
@@ -1168,100 +1258,109 @@ for parse errors. BEGIN, END is the parsed region."
   "For `indent-region-function', using the wisi indentation engine.
 If INDENT-BLANK-LINES is non-nil, also indent blank lines (for use as
 `indent-line-function')."
-  (when (< 0 wisi-debug)
-    (message "wisi-indent-region %d %d"
-            (wisi-safe-marker-pos begin)
-            (wisi-safe-marker-pos end)))
+  (if wisi-inhibit-parse
+      (when (< 0 wisi-debug)
+       (message "wisi-indent-region %d %d skipped; wisi-inhibit-parse"
+                (wisi-safe-marker-pos begin)
+                (wisi-safe-marker-pos end)))
 
-  (let ((wisi--parse-action 'indent)
-       (parse-required nil)
-       (end-mark (copy-marker end))
-       (prev-indent-failed wisi-indent-failed))
+    (let ((wisi--parse-action 'indent)
+         (parse-required nil)
+         (end-mark (copy-marker end))
+         (prev-indent-failed wisi-indent-failed))
 
-    (wisi--check-change)
+      (when (< 0 wisi-debug)
+       (message "wisi-indent-region %d %d"
+                (wisi-safe-marker-pos begin)
+                (wisi-safe-marker-pos end)))
 
-    ;; BEGIN is inclusive; END is exclusive.
-    (save-excursion
-      (goto-char begin)
-      (setq begin (line-beginning-position))
-
-      (when (bobp) (forward-line))
-      (while (and (not parse-required)
-                 (or (and (= begin end) (= (point) end))
-                     (< (point) end))
-                 (not (eobp)))
-       (unless (get-text-property (1- (point)) 'wisi-indent)
-         (setq parse-required t))
-       (forward-line))
-      )
-
-    ;; A parse either succeeds and sets the indent cache on all
-    ;; lines in the parsed region, or fails and leaves valid caches
-    ;; untouched.
-    (when (and parse-required
-              (or (not wisi-parse-failed)
-                  (wisi-parse-try 'indent)))
-
-      (wisi-set-parse-try nil)
-      (wisi--run-parse begin end)
-
-      ;; If there were errors corrected, the indentation is
-      ;; potentially ambiguous; see
-      ;; test/ada_mode-interactive_2.adb. Or it was a partial parse,
-      ;; where errors producing bad indent are pretty much expected.
-      (unless (wisi-partial-parse-p begin end)
-       (setq wisi-indent-failed (< 0 (+ (length (wisi-parser-lexer-errors 
wisi--parser))
-                                        (length (wisi-parser-parse-errors 
wisi--parser))))))
-      )
-
-    (if wisi-parse-failed
-       (progn
-         ;; primary indent failed
-         (setq wisi-indent-failed t)
-         (when (functionp wisi-indent-region-fallback)
-           (when (< 0 wisi-debug)
-             (message "wisi-indent-region fallback"))
-           (funcall wisi-indent-region-fallback begin end)))
+      (wisi--check-change)
 
+      ;; BEGIN is inclusive; END is exclusive.
       (save-excursion
-       ;; Apply cached indents.
        (goto-char begin)
-       (let ((wisi-indenting-p t))
-         (while (and (not (eobp))
-                     (or (and (= begin end) (= (point) end))
-                         (< (point) end-mark))) ;; end-mark is exclusive
-           (when (or indent-blank-lines (not (eolp)))
-             ;; ’indent-region’ doesn’t indent an empty line; ’indent-line’ 
does
-             (let ((indent (if (bobp) 0 (wisi--get-cached-indent begin end))))
-                   (indent-line-to indent))
-             )
-           (forward-line 1))
-
-         ;; Run wisi-indent-calculate-functions
-         (when wisi-indent-calculate-functions
-           (goto-char begin)
-           (while (and (not (eobp))
-                       (< (point) end-mark))
-             (back-to-indentation)
-             (let ((indent
-                    (run-hook-with-args-until-success 
'wisi-indent-calculate-functions)))
-               (when indent
-                 (indent-line-to indent)))
-
-             (forward-line 1)))
-         )
+       (setq begin (line-beginning-position))
+
+       (when (bobp) (forward-line))
+       (while (and (not parse-required)
+                   (or (and (= begin end) (= (point) end))
+                       (< (point) end))
+                   (not (eobp)))
+         (unless (get-text-property (1- (point)) 'wisi-indent)
+           (setq parse-required t))
+         (forward-line))
+       )
 
-       (when
-           (and prev-indent-failed
-                (not wisi-indent-failed))
-         ;; Previous parse failed or indent was potentially
-         ;; ambiguous, this one is not.
-         (goto-char end-mark)
-         (when (< 0 wisi-debug)
-           (message "wisi-indent-region post-parse-fail-hook"))
-         (run-hooks 'wisi-post-indent-fail-hook))
-       ))
-    ))
+      ;; A parse either succeeds and sets the indent cache on all
+      ;; lines in the parsed region, or fails and leaves valid caches
+      ;; untouched.
+      (when (and parse-required
+                (or (not wisi-parse-failed)
+                    (wisi-parse-try 'indent)))
+
+       (wisi-set-parse-try nil)
+       (wisi--run-parse begin end)
+
+       ;; If there were errors corrected, the indentation is
+       ;; potentially ambiguous; see
+       ;; test/ada_mode-interactive_2.adb. Or it was a partial parse,
+       ;; where errors producing bad indent are pretty much expected.
+       (unless (wisi-partial-parse-p begin end)
+         (setq wisi-indent-failed (< 0 (+ (length (wisi-parser-lexer-errors 
wisi--parser))
+                                          (length (wisi-parser-parse-errors 
wisi--parser))))))
+       )
+
+      (if wisi-parse-failed
+         (progn
+           ;; primary indent failed
+           (setq wisi-indent-failed t)
+           (when (functionp wisi-indent-region-fallback)
+             (when (< 0 wisi-debug)
+               (message "wisi-indent-region fallback"))
+             (funcall wisi-indent-region-fallback begin end)))
+
+       (save-excursion
+         ;; Apply cached indents. Start from end, so indenting
+         ;; doesn't affect correcting for errors in
+         ;; wisi--get-cached-indent.
+         (goto-char (1- end)) ;; end is exclusive
+         (goto-char (line-beginning-position))
+         (let ((wisi-indenting-p t))
+           (while (and (not (bobp))
+                       (or (and (= begin end) (= (point) end))
+                           (>= (point) begin)))
+             (when (or indent-blank-lines (not (eolp)))
+               ;; ’indent-region’ doesn’t indent an empty line; ’indent-line’ 
does
+               (let ((indent (if (bobp) 0 (wisi--get-cached-indent begin 
end))))
+                 (indent-line-to indent))
+               )
+             (forward-line -1))
+
+           ;; Run wisi-indent-calculate-functions
+           (when wisi-indent-calculate-functions
+             (goto-char begin)
+             (while (and (not (eobp))
+                         (< (point) end-mark))
+               (back-to-indentation)
+               (let ((indent
+                      (run-hook-with-args-until-success 
'wisi-indent-calculate-functions)))
+                 (when indent
+                   (indent-line-to indent)))
+
+               (forward-line 1)))
+           )
+
+         (when
+             (and prev-indent-failed
+                  (not wisi-indent-failed))
+           ;; Previous parse failed or indent was potentially
+           ;; ambiguous, this one is not.
+           (goto-char end-mark)
+           (when (< 0 wisi-debug)
+             (message "wisi-indent-region post-parse-fail-hook"))
+           (run-hooks 'wisi-post-indent-fail-hook))
+         ))
+      )))
 
 (defun wisi-indent-line ()
   "For `indent-line-function'."
@@ -1271,7 +1370,7 @@ If INDENT-BLANK-LINES is non-nil, also indent blank lines 
(for use as
     (when (>= (point) savep)
       (setq to-indent t))
 
-    (wisi-indent-region (line-beginning-position (- 
wisi-indent-context-lines)) (1+ (line-end-position)) t)
+    (wisi-indent-region (line-beginning-position (1+ (- 
wisi-indent-context-lines))) (1+ (line-end-position)) t)
 
     (goto-char savep)
     (when to-indent (back-to-indentation))
@@ -1286,12 +1385,18 @@ If INDENT-BLANK-LINES is non-nil, also indent blank 
lines (for use as
       (insert (wisi--lexer-error-inserted data)))
      ((wisi--parse-error-p data)
       (dolist (repair (wisi--parse-error-repair data))
+       (goto-char (wisi--parse-error-repair-pos repair))
        (when (< 0 (length (wisi--parse-error-repair-deleted repair)))
          (delete-region (car (wisi--parse-error-repair-deleted-region repair))
-                        (cdr (wisi--parse-error-repair-deleted-region 
repair))))
+                        (cdr (wisi--parse-error-repair-deleted-region repair)))
+         (when (= ?  (char-after (point)))
+           (delete-char 1)))
        (dolist (id (wisi--parse-error-repair-inserted repair))
-         (insert (cdr (assoc id (wisi-parser-repair-image wisi--parser))))
-         (insert " "))
+         (when (and (not (bobp))
+                    (not (= ?\( (char-before (point))))
+                    (member (syntax-class (syntax-after (1- (point)))) '(2 
3))) ;; word or symbol
+           (insert " "))
+         (insert (cdr (assoc id (wisi-parser-repair-image wisi--parser)))))
        ))
      )))
 
@@ -1302,7 +1407,6 @@ If INDENT-BLANK-LINES is non-nil, also indent blank lines 
(for use as
     (if (= 1 (+ (length (wisi-parser-lexer-errors wisi--parser))
                (length (wisi-parser-parse-errors wisi--parser))))
        (progn
-         (wisi-goto-error)
          (wisi-repair-error-1 (or (car (wisi-parser-lexer-errors wisi--parser))
                                   (car (wisi-parser-parse-errors 
wisi--parser)))))
       (if (buffer-live-p wisi-error-buffer)
@@ -1339,14 +1443,16 @@ If non-nil, only repair errors in BEG END region."
   "Match line number encoded into identifier by 
`wisi-xref-identifier-at-point'.")
 
 (defun wisi-xref-ident-make (identifier &optional other-function)
+  "Return an xref-item for IDENTIFIER."
   (let* ((t-prop (get-text-property 0 'xref-identifier identifier))
         ;; If t-prop is non-nil: identifier is from
         ;; identifier-at-point, the desired location is the ’other’
         ;; (spec/body).
         ;;
         ;; If t-prop is nil: identifier is from prompt/completion,
-        ;; the line number may be included in the identifier
-        ;; wrapped in <>, and the desired file is the current file.
+        ;; the line number may be included in the identifier wrapped
+        ;; in <>, and the desired location is that line in the current
+        ;; file.
         (ident
          (if t-prop
              (substring-no-properties identifier 0 nil)
@@ -1371,9 +1477,47 @@ If non-nil, only repair errors in BEG END region."
     (if t-prop
        (funcall other-function ident file line column)
 
-      (list (xref-make ident (xref-make-file-location file (or line 1) 
column)))
+      (xref-make ident (xref-make-file-location file (or line 1) column))
       )))
 
+(defun wisi-xref-item (identifier)
+  "Given IDENTIFIER, return an xref-item, with line, column nil if unknown.
+IDENTIFIER is from a user prompt with completion, or from
+`xref-backend-identifier-at-point'."
+  (let* ((t-prop (get-text-property 0 'xref-identifier identifier))
+        ;; If t-prop is non-nil: identifier is from
+        ;; identifier-at-point.
+        ;;
+        ;; If t-prop is nil: identifier is from prompt/completion,
+        ;; the line number may be included in the identifier
+        ;; wrapped in <>.
+        (ident
+         (if t-prop
+             (substring-no-properties identifier 0 nil)
+           (string-match wisi-xref-ident-regexp identifier)
+           (match-string 1 identifier)
+           ))
+        (file
+         (if t-prop
+             (plist-get t-prop ':file)
+           (buffer-file-name)))
+        (line
+         (if t-prop
+             (plist-get t-prop ':line)
+           (when (match-string 2 identifier)
+             (string-to-number (match-string 2 identifier)))))
+        (column
+         (when t-prop
+           (plist-get t-prop ':column)))
+        )
+
+    (unless (file-name-absolute-p file)
+      (setq file (locate-file file compilation-search-path)))
+
+    (let ((eieio-skip-typecheck t)) ;; allow line, column nil.
+      (xref-make ident (xref-make-file-location file line column)))
+    ))
+
 (defun wisi-xref-identifier-at-point ()
   (let ((ident (thing-at-point 'symbol)))
     (when ident
@@ -1414,45 +1558,57 @@ If non-nil, only repair errors in BEG END region."
   (let ((region (wisi-prev-name-region)))
     (buffer-substring-no-properties (car region) (cdr region))))
 
-(defun wisi-xref-identifier-completion-table ()
-  (wisi-validate-cache (point-min) (point-max) t 'navigate)
-  (let ((table nil)
-       (pos (point-min))
-       end-pos)
-    (while (setq pos (next-single-property-change pos 'wisi-name))
-      ;; We can’t store location data in a string text property -
-      ;; it does not survive completion. So we include the line
-      ;; number in the identifier string. This also serves to
-      ;; disambiguate overloaded identifiers.
-      (setq end-pos (next-single-property-change pos 'wisi-name))
-      (push
-       (format "%s<%d>"
-              (buffer-substring-no-properties pos end-pos)
-              (line-number-at-pos pos))
-       table)
-      (setq pos end-pos)
-      )
-    table))
+(defun wisi-names (append-lines)
+  "List of names; each is text from one 'wisi-name property in current buffer.
+If APPEND-LINES is non-nil, each has the line number it occurs on appended."
+  (when wisi--parser
+    ;; wisi--parser is nil in a non-language buffer, like Makefile
+    (wisi-validate-cache (point-min) (point-max) t 'navigate)
+    (let ((table nil)
+         (pos (point-min))
+         end-pos)
+      (while (setq pos (next-single-property-change pos 'wisi-name))
+       ;; We can’t store location data in a string text property -
+       ;; it does not survive completion. So we include the line
+       ;; number in the identifier string. This also serves to
+       ;; disambiguate overloaded identifiers in the user interface.
+       (setq end-pos (next-single-property-change pos 'wisi-name))
+       (push
+        (if append-lines
+            (format "%s<%d>"
+                    (buffer-substring-no-properties pos end-pos)
+                    (line-number-at-pos pos))
+          (buffer-substring-no-properties pos end-pos))
+        table)
+       (setq pos end-pos)
+       )
+      table)))
 
 ;;;; debugging
 
-(defun wisi-show-region (string)
-  (interactive "Mregion: ")
-  (when (not (= ?\( (aref string 0)))
-    (setq string (concat "(" string ")")))
+(defun wisi-show-region ()
+  (interactive)
+  (cond
+   ((use-region-p)
+    (message "(%s . %s)" (region-beginning) (region-end)))
+   (t
+    (let ((string (read-from-minibuffer "region: ")))
+      (when (not (= ?\( (aref string 0)))
+       (setq string (concat "(" string ")")))
 
-  (let ((region (read string)))
-    (cond
-     ((consp (cdr region))
-      ;; region is a list; (begin end)
-      (set-mark  (nth 0 region))
-      (goto-char (nth 1 region)))
-
-     ((consp region)
-      ;; region is a cons; (begin . end)
-      (set-mark  (car region))
-      (goto-char (cdr region)))
-     )))
+      (let ((region (read string)))
+       (cond
+        ((consp (cdr region))
+         ;; region is a list; (begin end)
+         (set-mark  (nth 0 region))
+         (goto-char (nth 1 region)))
+
+        ((consp region)
+         ;; region is a cons; (begin . end)
+         (set-mark  (car region))
+         (goto-char (cdr region)))
+        ))))
+   ))
 
 (defun wisi-debug-keys ()
   "Add debug key definitions to `global-map'."
@@ -1486,7 +1642,7 @@ If non-nil, only repair errors in BEG END region."
         'font-lock-face nil
         'fontified nil)))
      (wisi-validate-cache begin end t parse-action)
-     (when (fboundp 'font-lock-ensure) (font-lock-ensure))) ;; emacs < 25
+     (font-lock-ensure))
 
     (navigate
      (wisi-validate-cache begin end t parse-action))
@@ -1602,7 +1758,7 @@ If non-nil, only repair errors in BEG END region."
         (cons 'navigate nil)
         (cons 'indent nil)))
 
-  ;; file local variables may have added opentoken, gnatprep
+  ;; file local variables may have modified wisi-indent-calculate-functions
   (setq wisi-indent-calculate-functions (append 
wisi-indent-calculate-functions indent-calculate))
   (set (make-local-variable 'indent-line-function) #'wisi-indent-line)
   (set (make-local-variable 'indent-region-function) #'wisi-indent-region)
@@ -1615,18 +1771,14 @@ If non-nil, only repair errors in BEG END region."
   (add-hook 'after-change-functions #'wisi-after-change nil t)
   (setq wisi--change-end (copy-marker (point-min) t))
 
-  ;; See comments above on syntax-propertize.
-  (when (< emacs-major-version 25) (syntax-propertize (point-max)))
+  (set (make-local-variable 'comment-indent-function) 'wisi-comment-indent)
 
-  ;; In Emacs >= 26, ‘run-mode-hooks’ (in the major mode function)
-  ;; runs ‘hack-local-variables’ after ’*-mode-hooks’; we need
-  ;; ‘wisi-post-local-vars’ to run after ‘hack-local-variables’.
   (add-hook 'hack-local-variables-hook 'wisi-post-local-vars nil t)
   )
 
 (defun wisi-post-local-vars ()
   "See wisi-setup."
-  (setq hack-local-variables-hook (delq 'wisi-post-local-vars 
hack-local-variables-hook))
+  (remove-hook 'hack-local-variables-hook #'wisi-post-local-vars)
 
   (unless wisi-disable-face
     (jit-lock-register #'wisi-fontify-region)))
diff --git a/wisi.gpr b/wisi.gpr.gp
similarity index 93%
rename from wisi.gpr
rename to wisi.gpr.gp
index 27e890d..1c17529 100644
--- a/wisi.gpr
+++ b/wisi.gpr.gp
@@ -1,6 +1,7 @@
 --  Abstract :
 --
---  Make installed ELPA package wisi Ada code available for other projects.
+--  Make installed and source ELPA package wisi Ada code available for
+--  other projects.
 --
 --  Copyright (C) 2017, 2019 Free Software Foundation, Inc.
 --
@@ -18,6 +19,10 @@
 
 with "gnatcoll";
 with "standard_common";
+#if ELPA="no"
+with "sal";
+with "wisitoken";
+#end if;
 project Wisi is
 
    for Source_Dirs use (".");
diff --git a/wisitoken-bnf-generate.adb b/wisitoken-bnf-generate.adb
index 72a0221..a7616b4 100644
--- a/wisitoken-bnf-generate.adb
+++ b/wisitoken-bnf-generate.adb
@@ -52,7 +52,7 @@ is
    begin
       --  verbosity meaning is actually determined by output choice;
       --  they should be consistent with this description.
-      Put_Line (Standard_Error, "version 1.3.0");
+      Put_Line (Standard_Error, "version 1.3.1");
       Put_Line (Standard_Error, "wisitoken-bnf-generate [options] {wisi 
grammar file}");
       Put_Line (Standard_Error, "Generate source code implementing a parser 
for the grammar.");
       New_Line (Standard_Error);
diff --git a/wisitoken-bnf-output_ada_common.adb 
b/wisitoken-bnf-output_ada_common.adb
index 97ec9ed..ed4ef64 100644
--- a/wisitoken-bnf-output_ada_common.adb
+++ b/wisitoken-bnf-output_ada_common.adb
@@ -1008,7 +1008,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("result->buffer_last       = input + length - 1;");
       Indent_Line ("result->cursor            = input;");
       Indent_Line ("result->byte_token_start  = input;");
-      Indent_Line ("result->char_pos          = 1;");
+      Indent_Line ("result->char_pos          = 1; /* match 
WisiToken.Buffer_Region */");
       Indent_Line ("result->char_token_start  = 1;");
       Indent_Line ("result->line              = (*result->cursor == 0x0A) ? 2 
: 1;");
       Indent_Line ("result->line_token_start  = result->line;");
@@ -1064,19 +1064,23 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("#define YYPEEK() (lexer->cursor <= lexer->buffer_last) ? 
*lexer->cursor : 4");
       New_Line;
 
-      --  Don't count UTF-8 continuation bytes, or first byte of DOS newline
-      Indent_Line ("#define DO_COUNT ((*lexer->cursor & 0xC0) != 0xC0) && 
(*lexer->cursor != 0x0D)");
-      New_Line;
-
       Indent_Line ("static void skip(wisi_lexer* lexer)");
       Indent_Line ("{");
       Indent := Indent + 3;
       Indent_Line ("if (lexer->cursor <= lexer->buffer_last)");
-      Indent_Line ("{");
       Indent_Line ("   ++lexer->cursor;");
-      Indent_Line ("   if (DO_COUNT) ++lexer->char_pos;");
-      Indent_Line ("   if (lexer->cursor <= lexer->buffer_last)");
-      Indent_Line ("      if (*lexer->cursor == 0x0A) ++lexer->line;");
+      Indent_Line ("if (lexer->cursor <= lexer->buffer_last)");
+      Indent_Line ("{");
+      Indent_Line ("   /* UFT-8 encoding: 
https://en.wikipedia.org/wiki/UTF-8#Description */");
+      Indent_Line ("   if (*lexer->cursor == 0x0A && lexer->cursor > 
lexer->buffer && *(lexer->cursor - 1) == 0x0D)");
+      Indent_Line ("     {/* second byte of DOS line ending */");
+      Indent_Line ("     }");
+      Indent_Line ("   else if ((*lexer->cursor & 0x80) == 0x80 && 
(*lexer->cursor & 0xC0) != 0xC0)");
+      Indent_Line ("     {/* byte 2, 3 or 4 of multi-byte UTF-8 char */");
+      Indent_Line ("     }");
+      Indent_Line ("   else");
+      Indent_Line ("     ++lexer->char_pos;");
+      Indent_Line ("   if (*lexer->cursor == 0x0A) ++lexer->line;");
       Indent_Line ("}");
       Indent := Indent - 3;
       Indent_Line ("}");
@@ -1232,17 +1236,15 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("* {status = ERROR_unrecognized_character; continue;}");
 
       Put_Line ("*/");
-      Indent_Line ("}");
       Indent := Indent - 3;
+      Indent_Line ("}");
 
+      Indent_Line ("/* lexer->cursor and lexer ->char_pos are one char past 
end of token */");
       Indent_Line ("*byte_position = lexer->byte_token_start - lexer->buffer + 
1;");
       Indent_Line ("*byte_length   = lexer->cursor - 
lexer->byte_token_start;");
       Indent_Line ("*char_position = lexer->char_token_start;");
-      Indent_Line ("if (DO_COUNT)");
-      Indent_Line ("   *char_length = lexer->char_pos - 
lexer->char_token_start;");
-      Indent_Line ("else");
-      Indent_Line ("   *char_length = lexer->char_pos - 
lexer->char_token_start + 1;");
-      Indent_Line ("*line_start     = lexer->line_token_start;");
+      Indent_Line ("*char_length   = lexer->char_pos - 
lexer->char_token_start;");
+      Indent_Line ("*line_start    = lexer->line_token_start;");
       Indent_Line ("return status;");
       Indent_Line ("}");
       Indent := Indent - 3;
diff --git a/wisitoken-parse-lr-mckenzie_recover-explore.adb 
b/wisitoken-parse-lr-mckenzie_recover-explore.adb
index f749cfb..b6c9e43 100644
--- a/wisitoken-parse-lr-mckenzie_recover-explore.adb
+++ b/wisitoken-parse-lr-mckenzie_recover-explore.adb
@@ -546,7 +546,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
                   New_Config.Current_Shared_Token := Token.Min_Terminal_Index;
                end if;
             end if;
-            New_Config.Strategy_Counts (Explore_Table) := 
New_Config.Strategy_Counts (Explore_Table) + 1;
+            New_Config.Strategy_Counts (Push_Back) := 
New_Config.Strategy_Counts (Push_Back) + 1;
 
             Local_Config_Heap.Add (New_Config);
 
@@ -613,7 +613,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
             else
                Append (New_Config.Ops, (Undo_Reduce, Token.ID, Token_Count));
             end if;
-            New_Config.Strategy_Counts (Explore_Table) := 
New_Config.Strategy_Counts (Explore_Table) + 1;
+            New_Config.Strategy_Counts (Undo_Reduce) := 
New_Config.Strategy_Counts (Undo_Reduce) + 1;
 
             Local_Config_Heap.Add (New_Config);
 
@@ -695,7 +695,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
                            Do_Shift
                              ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action.State, ID,
                               Cost_Delta => 0,
-                              Strategy   => Explore_Table);
+                              Strategy   => Insert);
                         end;
 
                      when Reduce =>
@@ -714,7 +714,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
                               Do_Reduce_2
                                 ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
                                  Cost_Delta => 0,
-                                 Strategy   => Explore_Table);
+                                 Strategy   => Insert);
                            end;
 
                         else
@@ -724,7 +724,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
                               Do_Reduce_2
                                 ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
                                  Cost_Delta => 0,
-                                 Strategy   => Explore_Table);
+                                 Strategy   => Insert);
                            end;
                         end if;
 
@@ -1520,7 +1520,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             New_Config.Check_Status   := (Label => 
WisiToken.Semantic_Checks.Ok);
 
             New_Config.Cost := New_Config.Cost + McKenzie_Param.Delete (ID);
-            New_Config.Strategy_Counts (Explore_Table) := 
Config.Strategy_Counts (Explore_Table) + 1;
+            New_Config.Strategy_Counts (Delete) := Config.Strategy_Counts 
(Delete) + 1;
 
             if Matching_Push_Back then
                --  We are deleting a push_back; cancel the push_back cost, to 
make
diff --git a/wisitoken-parse-lr-mckenzie_recover.adb 
b/wisitoken-parse-lr-mckenzie_recover.adb
index c53f0ef..6fa41d8 100644
--- a/wisitoken-parse-lr-mckenzie_recover.adb
+++ b/wisitoken-parse-lr-mckenzie_recover.adb
@@ -2,7 +2,7 @@
 --
 --  See spec
 --
---  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2020 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -245,7 +245,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
       if Trace_McKenzie > Outline then
          Trace.New_Line;
-         Trace.Put_Line (System.Multiprocessors.CPU_Range'Image 
(Worker_Tasks'Last) & " parallel tasks");
+         Trace.Put_Line (Task_Count'Image & " parallel tasks");
       end if;
 
       for I in Worker_Tasks'First .. Task_Count loop
@@ -718,8 +718,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    when E : others =>
       if Debug_Mode then
          Trace.Put (Ada.Exceptions.Exception_Name (E) & ": " & 
Ada.Exceptions.Exception_Message (E));
+         raise;
+      else
+         return Fail_Programmer_Error;
       end if;
-      return Fail_Programmer_Error;
    end Recover;
 
    ----------
@@ -921,12 +923,20 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Config    : in out Configuration;
       Index     : in out WisiToken.Token_Index;
       ID        : in     Token_ID)
+   is begin
+      Check (Terminals (Index).ID, ID);
+      Delete (Terminals, Config, Index);
+   end Delete_Check;
+
+   procedure Delete
+     (Terminals : in     Base_Token_Arrays.Vector;
+      Config    : in out Configuration;
+      Index     : in out WisiToken.Token_Index)
    is
       use Config_Op_Arrays;
       use Sorted_Insert_Delete_Arrays;
-      Op : constant Config_Op := (Delete, ID, Index);
+      Op : constant Config_Op := (Delete, Terminals (Index).ID, Index);
    begin
-      Check (Terminals (Index).ID, ID);
       if Is_Full (Config.Ops) or Is_Full (Config.Insert_Delete) then
          raise Bad_Config;
       end if;
@@ -934,7 +944,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Insert (Config.Insert_Delete, Op);
       Config.Current_Insert_Delete := 1;
       Index := Index + 1;
-   end Delete_Check;
+   end Delete;
 
    procedure Find_ID
      (Config         : in     Configuration;
@@ -1057,10 +1067,22 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    end Find_Matching_Name;
 
    procedure Insert (Config : in out Configuration; ID : in Token_ID)
+   is begin
+      Insert (Config, Config.Current_Shared_Token, ID);
+   end Insert;
+
+   procedure Insert (Config : in out Configuration; IDs : in Token_ID_Array)
+   is begin
+      for ID of IDs loop
+         Insert (Config, ID);
+      end loop;
+   end Insert;
+
+   procedure Insert (Config : in out Configuration; Index : in 
WisiToken.Token_Index; ID : in Token_ID)
    is
       use Config_Op_Arrays;
       use Sorted_Insert_Delete_Arrays;
-      Op : constant Config_Op := (Insert, ID, Config.Current_Shared_Token, 
Unknown_State, 0);
+      Op : constant Config_Op := (Insert, ID, Index, Unknown_State, 0);
    begin
       if Is_Full (Config.Ops) or Is_Full (Config.Insert_Delete) then
          raise Bad_Config;
@@ -1070,13 +1092,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Config.Current_Insert_Delete := 1;
    end Insert;
 
-   procedure Insert (Config : in out Configuration; IDs : in Token_ID_Array)
-   is begin
-      for ID of IDs loop
-         Insert (Config, ID);
-      end loop;
-   end Insert;
-
    function Next_Token
      (Terminals                 :         in     Base_Token_Arrays.Vector;
       Terminals_Current         :         in out Base_Token_Index;
diff --git a/wisitoken-parse-lr-mckenzie_recover.ads 
b/wisitoken-parse-lr-mckenzie_recover.ads
index 9b84a03..dedad36 100644
--- a/wisitoken-parse-lr-mckenzie_recover.ads
+++ b/wisitoken-parse-lr-mckenzie_recover.ads
@@ -118,6 +118,12 @@ private
    --  order. Increments Index, for convenience when deleting several
    --  tokens.
 
+   procedure Delete
+     (Terminals : in     Base_Token_Arrays.Vector;
+      Config    : in out Configuration;
+      Index     : in out WisiToken.Token_Index);
+   --  Same as Delete_Check, without the check.
+
    procedure Find_ID
      (Config         : in     Configuration;
       ID             : in     Token_ID;
@@ -170,16 +176,19 @@ private
    --  Also count tokens with ID = Other_ID.
 
    procedure Insert (Config : in out Configuration; ID : in Token_ID);
-   --  Append an Insert op to Config.Ops, and insert it in
-   --  Config.Insert_Deleted in token_index order.
+   --  Append an Insert op at Config.Current_Shared_Token, to Config.Ops,
+   --  and insert it in Config.Insert_Deleted in token_index order.
 
    procedure Insert (Config : in out Configuration; IDs : in Token_ID_Array);
    --  Call Insert for each item in IDs.
 
+   procedure Insert (Config : in out Configuration; Index : in 
WisiToken.Token_Index; ID : in Token_ID);
+   --  Same as Insert, but at Index, not Config.Current_Shared_Token.
+
    function Next_Token
      (Terminals                 :         in     Base_Token_Arrays.Vector;
       Terminals_Current         :         in out Base_Token_Index;
-      Restore_Terminals_Current :         in out WisiToken.Base_Token_Index;
+      Restore_Terminals_Current :         in out Base_Token_Index;
       Insert_Delete             : aliased in out 
Sorted_Insert_Delete_Arrays.Vector;
       Current_Insert_Delete     :         in out SAL.Base_Peek_Type;
       Prev_Deleted              :         in     
Recover_Token_Index_Arrays.Vector)
diff --git a/wisitoken-parse-lr-parser.adb b/wisitoken-parse-lr-parser.adb
index 70fab20..44c1ac0 100644
--- a/wisitoken-parse-lr-parser.adb
+++ b/wisitoken-parse-lr-parser.adb
@@ -368,13 +368,16 @@ package body WisiToken.Parse.LR.Parser is
    is
       use all type Syntax_Trees.User_Data_Access;
    begin
-      Parser.Lexer                          := Lexer;
-      Parser.Trace                          := Trace;
+      Parser.Lexer     := Lexer;
+      Parser.Trace     := Trace;
+      Parser.User_Data := User_Data;
+
+      --  Terminals,  Line_Begin_Token are initialized to empty arrays.
+
       Parser.Table                          := Table;
       Parser.Language_Fixes                 := Language_Fixes;
       Parser.Language_Matching_Begin_Tokens := Language_Matching_Begin_Tokens;
       Parser.Language_String_ID_Set         := Language_String_ID_Set;
-      Parser.User_Data                      := User_Data;
 
       Parser.Enable_McKenzie_Recover := not McKenzie_Defaulted (Table.all);
 
@@ -701,18 +704,18 @@ package body WisiToken.Parse.LR.Parser is
                   if Ada.Text_IO.Is_Open (Shared_Parser.Recover_Log_File) then
                      declare
                         use Ada.Text_IO;
-                        Strategy_Counts : LR.Strategy_Counts := (others => 0);
                      begin
                         Put
                           (Shared_Parser.Recover_Log_File,
                            Ada.Calendar.Formatting.Image (Ada.Calendar.Clock) 
& " " &
-                             McKenzie_Recover.Recover_Status'Image 
(Recover_Result) & " " &
-                             SAL.Base_Peek_Type'Image 
(Pre_Recover_Parser_Count) & " '" &
+                             Shared_Parser.Partial_Parse_Active'Image & " " &
+                             Recover_Result'Image & " " &
+                             Pre_Recover_Parser_Count'Image & " '" &
                              Shared_Parser.Lexer.File_Name & "'");
 
                         Put (Shared_Parser.Recover_Log_File, '(');
                         for Parser of Shared_Parser.Parsers loop
-                           Accumulate (Parser.Recover, Strategy_Counts);
+                           Put (Shared_Parser.Recover_Log_File, Image 
(Parser.Recover.Results.Peek.Strategy_Counts));
                            Put
                              (Shared_Parser.Recover_Log_File,
                               Integer'Image (Parser.Recover.Enqueue_Count) &
@@ -721,7 +724,6 @@ package body WisiToken.Parse.LR.Parser is
                         end loop;
                         Put (Shared_Parser.Recover_Log_File, ')');
 
-                        Put (Shared_Parser.Recover_Log_File, Image 
(Strategy_Counts));
                         New_Line (Shared_Parser.Recover_Log_File);
                         Flush (Shared_Parser.Recover_Log_File);
                      end;
@@ -1064,6 +1066,9 @@ package body WisiToken.Parse.LR.Parser is
             Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_State_Ref;
          begin
             if Trace_Action > Outline then
+               if Trace_Action > Extra then
+                  Parser_State.Tree.Print_Tree (Descriptor, 
Parser_State.Tree.Root);
+               end if;
                Parser.Trace.Put_Line
                  (Integer'Image (Parser_State.Label) & ": root node: " & 
Parser_State.Tree.Image
                     (Parser_State.Tree.Root, Descriptor));
diff --git a/wisitoken-parse-lr-parser.ads b/wisitoken-parse-lr-parser.ads
index e84d65d..18f476e 100644
--- a/wisitoken-parse-lr-parser.ads
+++ b/wisitoken-parse-lr-parser.ads
@@ -109,6 +109,8 @@ package WisiToken.Parse.LR.Parser is
       Terminate_Same_State    : Boolean;
       Enable_McKenzie_Recover : Boolean;
       Recover_Log_File        : Ada.Text_IO.File_Type;
+      Partial_Parse_Active    : Boolean := False;
+      --  Partial_Parse_Active is only used in recover log messages.
    end record;
 
    overriding procedure Finalize (Object : in out LR.Parser.Parser);
diff --git a/wisitoken-parse-lr.ads b/wisitoken-parse-lr.ads
index 76a02d5..36f8889 100644
--- a/wisitoken-parse-lr.ads
+++ b/wisitoken-parse-lr.ads
@@ -546,7 +546,8 @@ package WisiToken.Parse.LR is
    --  which is true if they were copied from the parser stack, and not
    --  pushed by recover.
 
-   type Strategies is (Language_Fix, Minimal_Complete, Matching_Begin, 
Explore_Table, String_Quote);
+   type Strategies is
+     (Language_Fix, Minimal_Complete, Matching_Begin, Push_Back, Undo_Reduce, 
Insert, Delete, String_Quote);
 
    type Strategy_Counts is array (Strategies) of Natural;
    function Image is new SAL.Gen_Array_Image (Strategies, Natural, 
Strategy_Counts, Trimmed_Image);
diff --git a/wisitoken-parse.adb b/wisitoken-parse.adb
index ee7b1e8..3685b4e 100644
--- a/wisitoken-parse.adb
+++ b/wisitoken-parse.adb
@@ -21,6 +21,7 @@ package body WisiToken.Parse is
 
    function Next_Grammar_Token (Parser : in out Base_Parser) return Token_ID
    is
+      use all type Ada.Containers.Count_Type;
       use all type Syntax_Trees.User_Data_Access;
 
       Token : Base_Token;
@@ -38,12 +39,16 @@ package body WisiToken.Parse is
          if Token.Line /= Invalid_Line_Number then
             --  Some lexers don't support line numbers.
             if Parser.Lexer.First then
-               Parser.Line_Begin_Token.Set_First_Last (Line_Number_Type'First, 
Token.Line);
+               if Parser.Line_Begin_Token.Length = 0 then
+                  Parser.Line_Begin_Token.Set_First_Last (Token.Line, 
Token.Line);
+               else
+                  Parser.Line_Begin_Token.Set_First_Last 
(Parser.Line_Begin_Token.First_Index, Token.Line);
+               end if;
                Parser.Line_Begin_Token (Token.Line) := 
Parser.Terminals.Last_Index +
                  (if Token.ID >= Parser.Trace.Descriptor.First_Terminal then 1 
else 0);
 
             elsif Token.ID = Parser.Trace.Descriptor.EOI_ID then
-               Parser.Line_Begin_Token.Set_First_Last (Line_Number_Type'First, 
Token.Line + 1);
+               Parser.Line_Begin_Token.Set_First_Last 
(Parser.Line_Begin_Token.First_Index, Token.Line + 1);
                Parser.Line_Begin_Token (Token.Line + 1) := 
Parser.Terminals.Last_Index + 1;
             end if;
          end if;
diff --git a/wisitoken-parse.ads b/wisitoken-parse.ads
index c2fb500..edecb90 100644
--- a/wisitoken-parse.ads
+++ b/wisitoken-parse.ads
@@ -23,11 +23,20 @@ with WisiToken.Syntax_Trees;
 package WisiToken.Parse is
 
    type Base_Parser is abstract new Ada.Finalization.Limited_Controlled with 
record
-      Trace            : access WisiToken.Trace'Class;
-      Lexer            : WisiToken.Lexer.Handle;
-      User_Data        : WisiToken.Syntax_Trees.User_Data_Access;
-      Terminals        : aliased WisiToken.Base_Token_Arrays.Vector;
+      Trace     : access WisiToken.Trace'Class;
+      Lexer     : WisiToken.Lexer.Handle;
+      User_Data : WisiToken.Syntax_Trees.User_Data_Access;
+      Terminals : aliased WisiToken.Base_Token_Arrays.Vector;
+
       Line_Begin_Token : aliased WisiToken.Line_Begin_Token_Vectors.Vector;
+      --  Line_Begin_Token (I) is the index into Terminals of the first
+      --  grammar token on line I. Line_Begin_Token.First_Index is the first
+      --  line containing a grammar token (after leading comments). However,
+      --  if the only token on line I is a non_grammar token (ie a comment,
+      --  or a newline for a blank line), Line_Begin_Token (I) is the last
+      --  grammar token on the previous non-blank line. If Line (I) is a
+      --  non-first line in a multi-line token, Line_Begin_Token (I) is
+      --  Invalid_Token_Index.
    end record;
    --  Common to all parsers. Finalize should free any allocated objects.
 
diff --git a/wisitoken-semantic_checks.ads b/wisitoken-semantic_checks.ads
index 09517e1..c55371e 100644
--- a/wisitoken-semantic_checks.ads
+++ b/wisitoken-semantic_checks.ads
@@ -100,6 +100,7 @@ package WisiToken.Semantic_Checks is
       Nonterm                 : in Recover_Token)
      return Check_Status;
    pragma Inline (Terminate_Partial_Parse);
-   --  If Active, raise Wisitoken.Partial_Parse; otherwise return Ok.
+   --  If partial parse is complete, raise Wisitoken.Partial_Parse;
+   --  otherwise return Ok.
 
 end WisiToken.Semantic_Checks;
diff --git a/wisitoken_grammar_re2c.c b/wisitoken_grammar_re2c.c
index f4185ee..ef82744 100644
--- a/wisitoken_grammar_re2c.c
+++ b/wisitoken_grammar_re2c.c
@@ -59,7 +59,7 @@ wisi_lexer* wisitoken_grammar_new_lexer
    result->buffer_last       = input + length - 1;
    result->cursor            = input;
    result->byte_token_start  = input;
-   result->char_pos          = 1;
+   result->char_pos          = 1; /* match WisiToken.Buffer_Region */
    result->char_token_start  = 1;
    result->line              = (*result->cursor == 0x0A) ? 2 : 1;
    result->line_token_start  = result->line;
@@ -97,16 +97,22 @@ static void debug(wisi_lexer* lexer, int state, unsigned 
char ch)
 
 #define YYPEEK() (lexer->cursor <= lexer->buffer_last) ? *lexer->cursor : 4
 
-#define DO_COUNT ((*lexer->cursor & 0xC0) != 0xC0) && (*lexer->cursor != 0x0D)
-
 static void skip(wisi_lexer* lexer)
 {
    if (lexer->cursor <= lexer->buffer_last)
-   {
       ++lexer->cursor;
-      if (DO_COUNT) ++lexer->char_pos;
-      if (lexer->cursor <= lexer->buffer_last)
-         if (*lexer->cursor == 0x0A) ++lexer->line;
+   if (lexer->cursor <= lexer->buffer_last)
+   {
+      /* UFT-8 encoding: https://en.wikipedia.org/wiki/UTF-8#Description */
+      if (*lexer->cursor == 0x0A && lexer->cursor > lexer->buffer && 
*(lexer->cursor - 1) == 0x0D)
+        {/* second byte of DOS line ending */
+        }
+      else if ((*lexer->cursor & 0x80) == 0x80 && (*lexer->cursor & 0xC0) != 
0xC0)
+        {/* byte 2, 3 or 4 of multi-byte UTF-8 char */
+        }
+      else
+        ++lexer->char_pos;
+      if (*lexer->cursor == 0x0A) ++lexer->line;
    }
 }
 #define YYSKIP() skip(lexer)
@@ -173,7 +179,7 @@ int wisitoken_grammar_next_token
    while (*id == -1 && status == 0)
    {
 
-#line 177 "../wisitoken_grammar_re2c.c"
+#line 183 "../wisitoken_grammar_re2c.c"
 {
        YYCTYPE yych;
        unsigned int yyaccept = 0;
@@ -326,21 +332,21 @@ yy2:
        YYSKIP ();
 yy3:
        YYDEBUG(3, YYPEEK ());
-#line 255 "../wisitoken_grammar.re2c"
+#line 261 "../wisitoken_grammar.re2c"
        {status = ERROR_unrecognized_character; continue;}
-#line 332 "../wisitoken_grammar_re2c.c"
+#line 338 "../wisitoken_grammar_re2c.c"
 yy4:
        YYDEBUG(4, YYPEEK ());
        YYSKIP ();
        YYDEBUG(5, YYPEEK ());
-#line 253 "../wisitoken_grammar.re2c"
+#line 259 "../wisitoken_grammar.re2c"
        {*id =  36; continue;}
-#line 339 "../wisitoken_grammar_re2c.c"
+#line 345 "../wisitoken_grammar_re2c.c"
 yy6:
        YYDEBUG(6, YYPEEK ());
        YYSKIP ();
        YYDEBUG(7, YYPEEK ());
-#line 211 "../wisitoken_grammar.re2c"
+#line 217 "../wisitoken_grammar.re2c"
        { lexer->byte_token_start = lexer->cursor;
           lexer->char_token_start = lexer->char_pos;
           if (*lexer->cursor == 0x0A)
@@ -348,14 +354,14 @@ yy6:
           else
              lexer->line_token_start = lexer->line;
           continue; }
-#line 352 "../wisitoken_grammar_re2c.c"
+#line 358 "../wisitoken_grammar_re2c.c"
 yy8:
        YYDEBUG(8, YYPEEK ());
        YYSKIP ();
        YYDEBUG(9, YYPEEK ());
-#line 218 "../wisitoken_grammar.re2c"
+#line 224 "../wisitoken_grammar.re2c"
        {*id =  1; continue;}
-#line 359 "../wisitoken_grammar_re2c.c"
+#line 365 "../wisitoken_grammar_re2c.c"
 yy10:
        YYDEBUG(10, YYPEEK ());
        YYSKIP ();
@@ -532,9 +538,9 @@ yy12:
        }
 yy13:
        YYDEBUG(13, YYPEEK ());
-#line 240 "../wisitoken_grammar.re2c"
+#line 246 "../wisitoken_grammar.re2c"
        {*id =  23; continue;}
-#line 538 "../wisitoken_grammar_re2c.c"
+#line 544 "../wisitoken_grammar_re2c.c"
 yy14:
        YYDEBUG(14, YYPEEK ());
        yyaccept = 0;
@@ -695,37 +701,37 @@ yy15:
        YYDEBUG(15, YYPEEK ());
        YYSKIP ();
        YYDEBUG(16, YYPEEK ());
-#line 237 "../wisitoken_grammar.re2c"
+#line 243 "../wisitoken_grammar.re2c"
        {*id =  20; continue;}
-#line 701 "../wisitoken_grammar_re2c.c"
+#line 707 "../wisitoken_grammar_re2c.c"
 yy17:
        YYDEBUG(17, YYPEEK ());
        YYSKIP ();
        YYDEBUG(18, YYPEEK ());
-#line 245 "../wisitoken_grammar.re2c"
+#line 251 "../wisitoken_grammar.re2c"
        {*id =  28; continue;}
-#line 708 "../wisitoken_grammar_re2c.c"
+#line 714 "../wisitoken_grammar_re2c.c"
 yy19:
        YYDEBUG(19, YYPEEK ());
        YYSKIP ();
        YYDEBUG(20, YYPEEK ());
-#line 248 "../wisitoken_grammar.re2c"
+#line 254 "../wisitoken_grammar.re2c"
        {*id =  31; continue;}
-#line 715 "../wisitoken_grammar_re2c.c"
+#line 721 "../wisitoken_grammar_re2c.c"
 yy21:
        YYDEBUG(21, YYPEEK ());
        YYSKIP ();
        YYDEBUG(22, YYPEEK ());
-#line 241 "../wisitoken_grammar.re2c"
+#line 247 "../wisitoken_grammar.re2c"
        {*id =  24; continue;}
-#line 722 "../wisitoken_grammar_re2c.c"
+#line 728 "../wisitoken_grammar_re2c.c"
 yy23:
        YYDEBUG(23, YYPEEK ());
        YYSKIP ();
        YYDEBUG(24, YYPEEK ());
-#line 232 "../wisitoken_grammar.re2c"
+#line 238 "../wisitoken_grammar.re2c"
        {*id =  15; continue;}
-#line 729 "../wisitoken_grammar_re2c.c"
+#line 735 "../wisitoken_grammar_re2c.c"
 yy25:
        YYDEBUG(25, YYPEEK ());
        YYSKIP ();
@@ -746,16 +752,16 @@ yy25:
        }
 yy26:
        YYDEBUG(26, YYPEEK ());
-#line 239 "../wisitoken_grammar.re2c"
+#line 245 "../wisitoken_grammar.re2c"
        {*id =  22; continue;}
-#line 752 "../wisitoken_grammar_re2c.c"
+#line 758 "../wisitoken_grammar_re2c.c"
 yy27:
        YYDEBUG(27, YYPEEK ());
        YYSKIP ();
        YYDEBUG(28, YYPEEK ());
-#line 247 "../wisitoken_grammar.re2c"
+#line 253 "../wisitoken_grammar.re2c"
        {*id =  30; continue;}
-#line 759 "../wisitoken_grammar_re2c.c"
+#line 765 "../wisitoken_grammar_re2c.c"
 yy29:
        YYDEBUG(29, YYPEEK ());
        YYSKIP ();
@@ -777,9 +783,9 @@ yy29:
        }
 yy31:
        YYDEBUG(31, YYPEEK ());
-#line 249 "../wisitoken_grammar.re2c"
+#line 255 "../wisitoken_grammar.re2c"
        {*id =  32; continue;}
-#line 783 "../wisitoken_grammar_re2c.c"
+#line 789 "../wisitoken_grammar_re2c.c"
 yy32:
        YYDEBUG(32, YYPEEK ());
        yyaccept = 1;
@@ -792,9 +798,9 @@ yy32:
        }
 yy33:
        YYDEBUG(33, YYPEEK ());
-#line 230 "../wisitoken_grammar.re2c"
+#line 236 "../wisitoken_grammar.re2c"
        {*id =  13; continue;}
-#line 798 "../wisitoken_grammar_re2c.c"
+#line 804 "../wisitoken_grammar_re2c.c"
 yy34:
        YYDEBUG(34, YYPEEK ());
        YYSKIP ();
@@ -805,37 +811,37 @@ yy34:
        }
 yy35:
        YYDEBUG(35, YYPEEK ());
-#line 246 "../wisitoken_grammar.re2c"
+#line 252 "../wisitoken_grammar.re2c"
        {*id =  29; continue;}
-#line 811 "../wisitoken_grammar_re2c.c"
+#line 817 "../wisitoken_grammar_re2c.c"
 yy36:
        YYDEBUG(36, YYPEEK ());
        YYSKIP ();
        YYDEBUG(37, YYPEEK ());
-#line 238 "../wisitoken_grammar.re2c"
+#line 244 "../wisitoken_grammar.re2c"
        {*id =  21; continue;}
-#line 818 "../wisitoken_grammar_re2c.c"
+#line 824 "../wisitoken_grammar_re2c.c"
 yy38:
        YYDEBUG(38, YYPEEK ());
        YYSKIP ();
        YYDEBUG(39, YYPEEK ());
-#line 233 "../wisitoken_grammar.re2c"
+#line 239 "../wisitoken_grammar.re2c"
        {*id =  16; continue;}
-#line 825 "../wisitoken_grammar_re2c.c"
+#line 831 "../wisitoken_grammar_re2c.c"
 yy40:
        YYDEBUG(40, YYPEEK ());
        YYSKIP ();
        YYDEBUG(41, YYPEEK ());
-#line 234 "../wisitoken_grammar.re2c"
+#line 240 "../wisitoken_grammar.re2c"
        {*id =  17; continue;}
-#line 832 "../wisitoken_grammar_re2c.c"
+#line 838 "../wisitoken_grammar_re2c.c"
 yy42:
        YYDEBUG(42, YYPEEK ());
        YYSKIP ();
        YYDEBUG(43, YYPEEK ());
-#line 242 "../wisitoken_grammar.re2c"
+#line 248 "../wisitoken_grammar.re2c"
        {*id =  25; continue;}
-#line 839 "../wisitoken_grammar_re2c.c"
+#line 845 "../wisitoken_grammar_re2c.c"
 yy44:
        YYDEBUG(44, YYPEEK ());
        yyaccept = 2;
@@ -964,23 +970,23 @@ yy45:
        }
 yy46:
        YYDEBUG(46, YYPEEK ());
-#line 250 "../wisitoken_grammar.re2c"
+#line 256 "../wisitoken_grammar.re2c"
        {*id =  33; continue;}
-#line 970 "../wisitoken_grammar_re2c.c"
+#line 976 "../wisitoken_grammar_re2c.c"
 yy47:
        YYDEBUG(47, YYPEEK ());
        YYSKIP ();
        YYDEBUG(48, YYPEEK ());
-#line 236 "../wisitoken_grammar.re2c"
+#line 242 "../wisitoken_grammar.re2c"
        {*id =  19; continue;}
-#line 977 "../wisitoken_grammar_re2c.c"
+#line 983 "../wisitoken_grammar_re2c.c"
 yy49:
        YYDEBUG(49, YYPEEK ());
        YYSKIP ();
        YYDEBUG(50, YYPEEK ());
-#line 244 "../wisitoken_grammar.re2c"
+#line 250 "../wisitoken_grammar.re2c"
        {*id =  27; continue;}
-#line 984 "../wisitoken_grammar_re2c.c"
+#line 990 "../wisitoken_grammar_re2c.c"
 yy51:
        YYDEBUG(51, YYPEEK ());
        yyaccept = 2;
@@ -1045,23 +1051,23 @@ yy57:
        YYDEBUG(57, YYPEEK ());
        YYSKIP ();
        YYDEBUG(58, YYPEEK ());
-#line 235 "../wisitoken_grammar.re2c"
+#line 241 "../wisitoken_grammar.re2c"
        {*id =  18; continue;}
-#line 1051 "../wisitoken_grammar_re2c.c"
+#line 1057 "../wisitoken_grammar_re2c.c"
 yy59:
        YYDEBUG(59, YYPEEK ());
        YYSKIP ();
        YYDEBUG(60, YYPEEK ());
-#line 229 "../wisitoken_grammar.re2c"
+#line 235 "../wisitoken_grammar.re2c"
        {*id =  12; continue;}
-#line 1058 "../wisitoken_grammar_re2c.c"
+#line 1064 "../wisitoken_grammar_re2c.c"
 yy61:
        YYDEBUG(61, YYPEEK ());
        YYSKIP ();
        YYDEBUG(62, YYPEEK ());
-#line 243 "../wisitoken_grammar.re2c"
+#line 249 "../wisitoken_grammar.re2c"
        {*id =  26; continue;}
-#line 1065 "../wisitoken_grammar_re2c.c"
+#line 1071 "../wisitoken_grammar_re2c.c"
 yy63:
        YYDEBUG(63, YYPEEK ());
        YYSKIP ();
@@ -1587,9 +1593,9 @@ yy72:
        }
 yy73:
        YYDEBUG(73, YYPEEK ());
-#line 251 "../wisitoken_grammar.re2c"
+#line 257 "../wisitoken_grammar.re2c"
        {*id =  34; continue;}
-#line 1593 "../wisitoken_grammar_re2c.c"
+#line 1599 "../wisitoken_grammar_re2c.c"
 yy74:
        YYDEBUG(74, YYPEEK ());
        YYSKIP ();
@@ -1924,23 +1930,23 @@ yy80:
        YYDEBUG(80, YYPEEK ());
        YYSKIP ();
        YYDEBUG(81, YYPEEK ());
-#line 228 "../wisitoken_grammar.re2c"
+#line 234 "../wisitoken_grammar.re2c"
        {*id =  11; skip_to(lexer, ")%"); continue;}
-#line 1930 "../wisitoken_grammar_re2c.c"
+#line 1936 "../wisitoken_grammar_re2c.c"
 yy82:
        YYDEBUG(82, YYPEEK ());
        YYSKIP ();
        YYDEBUG(83, YYPEEK ());
-#line 227 "../wisitoken_grammar.re2c"
+#line 233 "../wisitoken_grammar.re2c"
        {*id =  10; skip_to(lexer, "]%"); continue;}
-#line 1937 "../wisitoken_grammar_re2c.c"
+#line 1943 "../wisitoken_grammar_re2c.c"
 yy84:
        YYDEBUG(84, YYPEEK ());
        YYSKIP ();
        YYDEBUG(85, YYPEEK ());
-#line 226 "../wisitoken_grammar.re2c"
+#line 232 "../wisitoken_grammar.re2c"
        {*id =  9; skip_to(lexer, "}%"); continue;}
-#line 1944 "../wisitoken_grammar_re2c.c"
+#line 1950 "../wisitoken_grammar_re2c.c"
 yy86:
        YYDEBUG(86, YYPEEK ());
        YYSKIP ();
@@ -2109,9 +2115,9 @@ yy88:
        }
 yy89:
        YYDEBUG(89, YYPEEK ());
-#line 252 "../wisitoken_grammar.re2c"
+#line 258 "../wisitoken_grammar.re2c"
        {*id =  35; continue;}
-#line 2115 "../wisitoken_grammar_re2c.c"
+#line 2121 "../wisitoken_grammar_re2c.c"
 yy90:
        YYDEBUG(90, YYPEEK ());
        YYSKIP ();
@@ -2639,9 +2645,9 @@ yy97:
        }
 yy99:
        YYDEBUG(99, YYPEEK ());
-#line 219 "../wisitoken_grammar.re2c"
+#line 225 "../wisitoken_grammar.re2c"
        {*id =  2; continue;}
-#line 2645 "../wisitoken_grammar_re2c.c"
+#line 2651 "../wisitoken_grammar_re2c.c"
 yy100:
        YYDEBUG(100, YYPEEK ());
        YYSKIP ();
@@ -3118,9 +3124,9 @@ yy108:
        }
 yy109:
        YYDEBUG(109, YYPEEK ());
-#line 222 "../wisitoken_grammar.re2c"
+#line 228 "../wisitoken_grammar.re2c"
        {*id =  5; continue;}
-#line 3124 "../wisitoken_grammar_re2c.c"
+#line 3130 "../wisitoken_grammar_re2c.c"
 yy110:
        YYDEBUG(110, YYPEEK ());
        yyaccept = 2;
@@ -3155,9 +3161,9 @@ yy113:
        YYDEBUG(113, YYPEEK ());
        YYSKIP ();
        YYDEBUG(114, YYPEEK ());
-#line 231 "../wisitoken_grammar.re2c"
+#line 237 "../wisitoken_grammar.re2c"
        {*id =  14; continue;}
-#line 3161 "../wisitoken_grammar_re2c.c"
+#line 3167 "../wisitoken_grammar_re2c.c"
 yy115:
        YYDEBUG(115, YYPEEK ());
        YYSKIP ();
@@ -3624,9 +3630,9 @@ yy122:
        }
 yy123:
        YYDEBUG(123, YYPEEK ());
-#line 221 "../wisitoken_grammar.re2c"
+#line 227 "../wisitoken_grammar.re2c"
        {*id =  4; continue;}
-#line 3630 "../wisitoken_grammar_re2c.c"
+#line 3636 "../wisitoken_grammar_re2c.c"
 yy124:
        YYDEBUG(124, YYPEEK ());
        yyaccept = 2;
@@ -3783,9 +3789,9 @@ yy127:
        }
 yy128:
        YYDEBUG(128, YYPEEK ());
-#line 220 "../wisitoken_grammar.re2c"
+#line 226 "../wisitoken_grammar.re2c"
        {*id =  3; continue;}
-#line 3789 "../wisitoken_grammar_re2c.c"
+#line 3795 "../wisitoken_grammar_re2c.c"
 yy129:
        YYDEBUG(129, YYPEEK ());
        yyaccept = 2;
@@ -3962,9 +3968,9 @@ yy134:
        }
 yy135:
        YYDEBUG(135, YYPEEK ());
-#line 225 "../wisitoken_grammar.re2c"
+#line 231 "../wisitoken_grammar.re2c"
        {*id =  8; continue;}
-#line 3968 "../wisitoken_grammar_re2c.c"
+#line 3974 "../wisitoken_grammar_re2c.c"
 yy136:
        YYDEBUG(136, YYPEEK ());
        yyaccept = 2;
@@ -4111,9 +4117,9 @@ yy138:
        }
 yy139:
        YYDEBUG(139, YYPEEK ());
-#line 223 "../wisitoken_grammar.re2c"
+#line 229 "../wisitoken_grammar.re2c"
        {*id =  6; continue;}
-#line 4117 "../wisitoken_grammar_re2c.c"
+#line 4123 "../wisitoken_grammar_re2c.c"
 yy140:
        YYDEBUG(140, YYPEEK ());
        yyaccept = 2;
@@ -4280,20 +4286,18 @@ yy144:
        }
 yy145:
        YYDEBUG(145, YYPEEK ());
-#line 224 "../wisitoken_grammar.re2c"
+#line 230 "../wisitoken_grammar.re2c"
        {*id =  7; continue;}
-#line 4286 "../wisitoken_grammar_re2c.c"
+#line 4292 "../wisitoken_grammar_re2c.c"
 }
-#line 256 "../wisitoken_grammar.re2c"
+#line 262 "../wisitoken_grammar.re2c"
 
-      }
+   }
+   /* lexer->cursor and lexer ->char_pos are one char past end of token */
    *byte_position = lexer->byte_token_start - lexer->buffer + 1;
    *byte_length   = lexer->cursor - lexer->byte_token_start;
    *char_position = lexer->char_token_start;
-   if (DO_COUNT)
-      *char_length = lexer->char_pos - lexer->char_token_start;
-   else
-      *char_length = lexer->char_pos - lexer->char_token_start + 1;
-   *line_start     = lexer->line_token_start;
+   *char_length   = lexer->char_pos - lexer->char_token_start;
+   *line_start    = lexer->line_token_start;
    return status;
    }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]