mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-16 09:53:36 +00:00
When a user writes a clause for a predicate (or function) that does not exist
with that arity, but does exist with one or more other arities, report not
just the list of the other arity/arities, but, for each such other arity,
a diff between the declared arg types and the inferred arg types.
After this diff, we generate output like this:
bad_pred_arity.m:027: Error: clause for predicate `bad_pred_arity.p'/4
bad_pred_arity.m:027: without corresponding `:- pred' declaration.
bad_pred_arity.m:027: However, predicates of that name do exist with arities
bad_pred_arity.m:027: 3 and 5.
bad_pred_arity.m:027: Inferred :- pred p(int, string, int, string).
bad_pred_arity.m:027: The argument list difference from the arity 3 version
bad_pred_arity.m:027: is
bad_pred_arity.m:027: pred(
bad_pred_arity.m:027: int,
bad_pred_arity.m:027: + string,
bad_pred_arity.m:027: int,
bad_pred_arity.m:027: string
bad_pred_arity.m:027: )
bad_pred_arity.m:027: The argument list difference from the arity 5 version
bad_pred_arity.m:027: is
bad_pred_arity.m:027: pred(
bad_pred_arity.m:027: int,
bad_pred_arity.m:027: - float,
bad_pred_arity.m:027: string,
bad_pred_arity.m:027: int,
bad_pred_arity.m:027: string
bad_pred_arity.m:027: )
compiler/typecheck_errors.m:
Generate the diff part of the message above.
compiler/typecheck.m:
Invoke typecheck_errors.m when relevant.
compiler/error_util.m:
When comparing two error_specs, switch from a two-level comparison
(first the contexts of error_msgs, then everything else) to three levels
first the contexts of error_msgs, then their error_msg_components,
then everything else). This is needed to allow the error message from
make_hlds_error.m (which reports the error and mentions the arities
with which the named predicate or function does exist) come out before
the informational message from typecheck.m that prints the inferred
arg types and their differences from the other arities. (With the old
comparison, the difference in severity would trump the invisible order
components that this diff includes in both specs to force the desire
order.)
Base the code comparing error_specs on the code for comparing error_msgs.
Move the two previously separate pieces code for those tasks next to each
other.
compiler/make_hlds_error.m:
Add the invisble ordering component.
When we see clauses with two or more wrong arities for a given predicate
or function, don't list the automatically created pred declaration
for an *earlier* wrong-arity clause as a real declaration whose arity
is to be listed in the error messages we generate for *later* wrong-arity
clauses.
Add some documentation.
compiler/add_pred.m:
Factor out some common code.
library/edit_seq.m:
A new module for computing diffs.
library/library.m:
library/MODULES_DOC:
Add the new module to the standard library.
tests/hard_coded/edit_seq_test.{m,exp}:
A new test case for the diff algorithm.
tests/invalid/bad_pred_arity.{m,err_exp}:
A new test case for the new error message.
tests/hard_coded/Mmakefile:
tests/invalid/Mmakefile:
Enable the new test cases.
tests/invalid/bigtest.err_exp:
tests/invalid/bug197.err_exp:
tests/invalid/bug278.err_exp:
tests/invalid/errors2.err_exp:
tests/invalid/invalid_binary_literal.err_exp:
tests/invalid/invalid_float_literal.err_exp:
tests/invalid/invalid_hex_literal.err_exp:
tests/invalid/invalid_main.err_exp:
tests/invalid/invalid_octal_literal.err_exp:
tests/invalid/multimode_dcg.err_exp:
tests/invalid/multisoln_func.err_exp:
tests/invalid/null_char.err_exp:
tests/invalid/state_vars_test3.err_exp:
tests/invalid/try_detism.err_exp2:
tests/invalid/typeclass_test_5.err_exp:
tests/invalid/typeclass_test_8.err_exp:
tests/invalid/unsatisfiable_constraint.err_exp:
tests/invalid_purity/impure_func_t3.err_exp:
Update these files to expect error messages in the new order.
samples/diff/*.m:
Fix comments, mostly by moving them to where our programming style
wants them.
1087 lines
37 KiB
Mathematica
1087 lines
37 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1995-1998, 2006, 2011 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Main author: bromage
|
|
% Based on diffs.m, written by bromage and simplified by
|
|
% Marnix Klooster <marnix@worldonline.nl>
|
|
%
|
|
% This module contains the predicates to display a diff in various
|
|
% output styles, based on the command-line options supplied.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module diff_out.
|
|
:- interface.
|
|
|
|
:- import_module difftype.
|
|
:- import_module file.
|
|
|
|
:- import_module io.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type output_style
|
|
---> normal
|
|
; help_only
|
|
; version_only
|
|
; context(int)
|
|
; unified(int)
|
|
; ed
|
|
; forward_ed
|
|
; rcs
|
|
; ifdef(string)
|
|
; brief
|
|
; side_by_side
|
|
; cvs_merge_conflict.
|
|
|
|
% The default output style.
|
|
%
|
|
:- pred default_output_style(output_style::out) is det.
|
|
|
|
% Succeeds if, for this output style, an absence of differences
|
|
% means that no output should be generated.
|
|
%
|
|
:- pred no_diff_implies_no_output(output_style::in) is semidet.
|
|
|
|
% Succeeds if the user only wants to know about the presence
|
|
% of any differences, not what they actually are.
|
|
%
|
|
:- pred full_diff_not_required(output_style::in) is semidet.
|
|
|
|
% Succeeds if the output style is "robust", that is, the absence of a
|
|
% newline at the end of the file actually matters.
|
|
%
|
|
:- pred robust(output_style::in) is semidet.
|
|
|
|
% display_diff takes a diff and displays it in the user's specified output
|
|
% format.
|
|
%
|
|
:- pred display_diff(file::in, file::in, diff::in, io::di, io::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module globals.
|
|
:- import_module options.
|
|
|
|
:- import_module bool.
|
|
:- import_module char.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module string.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
default_output_style(normal).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
no_diff_implies_no_output(normal).
|
|
no_diff_implies_no_output(context(_)).
|
|
no_diff_implies_no_output(unified(_)).
|
|
no_diff_implies_no_output(ed).
|
|
no_diff_implies_no_output(forward_ed).
|
|
no_diff_implies_no_output(rcs).
|
|
no_diff_implies_no_output(brief).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
full_diff_not_required(brief).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
robust(normal).
|
|
robust(context(_)).
|
|
robust(unified(_)).
|
|
robust(rcs).
|
|
robust(ifdef(_)).
|
|
robust(side_by_side).
|
|
robust(cvs_merge_conflict).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% show_file shows the segment of the file from Low to High, with
|
|
% each line preceded by the Prefix character and a space. The diff(1)
|
|
% format specifies that the lines effected in the first file should be
|
|
% flagged by '<' and the lines effected in the second file should be
|
|
% flagged by '>'.
|
|
%
|
|
:- pred show_file(file::in, string::in, pos::in, pos::in,
|
|
io::di, io::uo) is det.
|
|
|
|
show_file(File, Prefix, Low, High, !IO) :-
|
|
globals.io_lookup_bool_option(expand_tabs, ExpandTabs, !IO),
|
|
show_file_2(ExpandTabs, File, Prefix, Low, High, !IO).
|
|
|
|
:- pred show_file_2(bool::in, file::in, string::in, pos::in, pos::in,
|
|
io::di, io::uo) is det.
|
|
|
|
show_file_2(ExpandTabs, File, Prefix, Low, High, !IO) :-
|
|
( if Low < High then
|
|
( if file.get_line(File, Low, Line) then
|
|
io.write_string(Prefix, !IO),
|
|
(
|
|
ExpandTabs = yes,
|
|
string.to_char_list(Line, LineList),
|
|
expand_tabs(LineList, 0, !IO)
|
|
;
|
|
ExpandTabs = no,
|
|
io.write_string(Line, !IO)
|
|
),
|
|
show_file_2(ExpandTabs, File, Prefix, Low + 1, High, !IO)
|
|
else
|
|
error("diff_out_show_file: file ended prematurely")
|
|
)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred expand_tabs(list(char)::in, int::in, io::di, io::uo) is det.
|
|
|
|
expand_tabs([], _, !IO).
|
|
expand_tabs([C | Cs], Pos, !IO) :-
|
|
( if C = '\t' then
|
|
Spaces = tab_width - (Pos rem tab_width),
|
|
put_spaces(Spaces, Pos, NewPos, !IO),
|
|
expand_tabs(Cs, NewPos, !IO)
|
|
else
|
|
io.write_char(C, !IO),
|
|
expand_tabs(Cs, Pos + 1, !IO)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff: Determine which output style to use, then call the
|
|
% predicate to display that output.
|
|
%
|
|
% Some of these options (notably the ones which require no output) should
|
|
% have been handled already by the time we reach here. In those cases, we
|
|
% just call error/1.
|
|
%
|
|
display_diff(File1, File2, Diff, !IO) :-
|
|
globals.io_get_output_style(OutputStyle, !IO),
|
|
( if
|
|
Diff = [],
|
|
no_diff_implies_no_output(OutputStyle)
|
|
then
|
|
true
|
|
else
|
|
display_diff_2(OutputStyle, File1, File2, Diff, !IO)
|
|
).
|
|
|
|
:- pred display_diff_2(output_style::in, file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_diff_2(normal, File1, File2, Diff, !IO) :-
|
|
display_diff_normal(File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(help_only, _File1, _File2, _Diff, !IO) :-
|
|
error("display_diff: help_only").
|
|
|
|
display_diff_2(version_only, _File1, _File2, _Diff, !IO) :-
|
|
error("display_diff: version_only").
|
|
|
|
display_diff_2(context(Context), File1, File2, Diff, !IO) :-
|
|
display_context_diff(Context, File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(unified(Context), File1, File2, Diff, !IO) :-
|
|
display_unified_diff(Context, File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(ed, File1, File2, Diff, !IO) :-
|
|
display_diff_ed(File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(forward_ed, File1, File2, Diff, !IO) :-
|
|
display_diff_forward_ed(File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(rcs, File1, File2, Diff, !IO) :-
|
|
display_diff_rcs(File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(ifdef(Sym), File1, File2, Diff, !IO) :-
|
|
display_diff_ifdef(Sym, File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(brief, File1, File2, _Diff, !IO) :-
|
|
% XXX For this output style, we really don't need to perform
|
|
% a complete diff. This should be handled higher up for efficiency.
|
|
file.get_file_name(File1, FileName1),
|
|
file.get_file_name(File2, FileName2),
|
|
io.write_strings(["Files ", FileName1, " and ",
|
|
FileName2, " differ\n"], !IO).
|
|
|
|
display_diff_2(side_by_side, File1, File2, Diff, !IO) :-
|
|
display_diff_side_by_side(File1, File2, Diff, !IO).
|
|
|
|
display_diff_2(cvs_merge_conflict, File1, File2, Diff, !IO) :-
|
|
display_diff_cvs_merge_conflict(File1, File2, Diff, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff_normal takes a diff and displays it
|
|
% in the standard diff(1) output format.
|
|
%
|
|
:- pred display_diff_normal(file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_diff_normal(File1, File2, Diff, !IO) :-
|
|
globals.io_lookup_bool_option(initial_tab, InitialTab, !IO),
|
|
(
|
|
InitialTab = no,
|
|
FromStr = "< ",
|
|
ToStr = "> "
|
|
;
|
|
InitialTab = yes,
|
|
FromStr = "<\t",
|
|
ToStr = ">\t"
|
|
),
|
|
display_diff_normal_2(File1, File2, Diff, FromStr, ToStr, !IO).
|
|
|
|
% display_diff_normal takes a diff and displays it
|
|
% in the standard diff(1) output format.
|
|
%
|
|
:- pred display_diff_normal_2(file::in, file::in, diff::in, string::in,
|
|
string::in, io::di, io::uo) is det.
|
|
|
|
display_diff_normal_2(_, _, [], _, _, !IO).
|
|
display_diff_normal_2(File1, File2, [SingDiff | Diff], FromStr, ToStr, !IO) :-
|
|
(
|
|
SingDiff = add(X, Y1 - Y2),
|
|
diff_out.write_command(X - X, 'a', Y1 - Y2, !IO),
|
|
diff_out.show_file(File2, ToStr, Y1, Y2, !IO)
|
|
;
|
|
SingDiff = delete(X1 - X2, Y),
|
|
diff_out.write_command(X1 - X2, 'd', Y - Y, !IO),
|
|
diff_out.show_file(File1, FromStr, X1, X2, !IO)
|
|
;
|
|
SingDiff = change(X1 - X2, Y1 - Y2),
|
|
diff_out.write_command(X1 - X2, 'c', Y1 - Y2, !IO),
|
|
diff_out.show_file(File1, FromStr, X1, X2, !IO),
|
|
io.write_string("---\n", !IO),
|
|
diff_out.show_file(File2, ToStr, Y1, Y2, !IO)
|
|
),
|
|
display_diff_normal_2(File1, File2, Diff, FromStr, ToStr, !IO).
|
|
|
|
% write_command displays a diff(1) command.
|
|
% Like ed(1), a pair of numbers which are identical are abbreviated by a
|
|
% single number.
|
|
% MK: Assumption X=<X2
|
|
% AJB: And, similarly, Y=<Y2. This is actually an invariant of the
|
|
% segment type. See difftype.m.
|
|
%
|
|
:- pred write_command(segment::in, char::in, segment::in,
|
|
io::di, io::uo) is det.
|
|
|
|
write_command(X - X2, C, Y - Y2, !IO) :-
|
|
X1 = X + 1, % Convert from pos to line number.
|
|
( if X1 >= X2 then
|
|
% Either empty or singleton segment.
|
|
io.write_int(X2, !IO)
|
|
else
|
|
io.write_int(X1, !IO),
|
|
io.write_char(',', !IO),
|
|
io.write_int(X2, !IO)
|
|
),
|
|
io.write_char(C, !IO),
|
|
Y1 = Y + 1, % Convert from pos to line number.
|
|
( if Y1 >= Y2 then
|
|
% Either empty or singleton segment.
|
|
io.write_int(Y2, !IO)
|
|
else
|
|
io.write_int(Y1, !IO),
|
|
io.write_char(',', !IO),
|
|
io.write_int(Y2, !IO)
|
|
),
|
|
io.write_char('\n', !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff_rcs takes a diff and displays it in the RCS difference
|
|
% format.
|
|
%
|
|
:- pred display_diff_rcs(file::in, file::in, diff::in, io::di, io::uo) is det.
|
|
|
|
display_diff_rcs(_File1, _File2, [], !IO).
|
|
display_diff_rcs(File1, File2, [Cmd | Diff], !IO) :-
|
|
(
|
|
Cmd = add(X, Y1 - Y2),
|
|
write_command_rcs('a', X, Y2 - Y1, !IO),
|
|
show_file(File2, "", Y1, Y2, !IO)
|
|
;
|
|
Cmd = delete(X1 - X2, _Y),
|
|
write_command_rcs('d', X1, X2 - X1, !IO)
|
|
;
|
|
Cmd = change(X1 - X2, Y1 - Y2),
|
|
write_command_rcs('d', X1, X2 - X1, !IO),
|
|
write_command_rcs('a', X1, Y2 - Y1, !IO),
|
|
show_file(File2, "", Y1, Y2, !IO)
|
|
),
|
|
display_diff_rcs(File1, File2, Diff, !IO).
|
|
|
|
% diff_out.write_command_rcs displays a diff command in the RCS ,v format.
|
|
%
|
|
:- pred write_command_rcs(char::in, int::in, int::in, io::di, io::uo) is det.
|
|
|
|
write_command_rcs(C, X, Y, !IO) :-
|
|
io.write_char(C, !IO),
|
|
io.write_int(X + 1, !IO), % Convert from pos to line number.
|
|
io.write_char(' ', !IO),
|
|
io.write_int(Y, !IO),
|
|
io.write_char('\n', !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff_ed takes a diff and displays it in ed(1) format, but with
|
|
% all diffs backward.
|
|
%
|
|
:- pred display_diff_ed(file::in, file::in, diff::in, io::di, io::uo) is det.
|
|
|
|
display_diff_ed(_File1, _File2, [], !IO).
|
|
display_diff_ed(File1, File2, [Cmd | Diff], !IO) :-
|
|
display_diff_ed(File1, File2, Diff, !IO),
|
|
(
|
|
Cmd = add(X, Y1 - Y2),
|
|
write_command_ed(X - X, 'a', !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_string(".\n", !IO)
|
|
;
|
|
Cmd = delete(X, _Y),
|
|
write_command_ed(X, 'd', !IO)
|
|
;
|
|
Cmd = change(X, Y1 - Y2),
|
|
write_command_ed(X, 'c', !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_string(".\n", !IO)
|
|
).
|
|
|
|
% write_command_ed displays an ed(1) command.
|
|
%
|
|
:- pred write_command_ed(segment::in, char::in, io::di, io::uo) is det.
|
|
|
|
write_command_ed(X - X2, C, !IO) :-
|
|
X1 = X + 1, % Convert from pos to line number
|
|
( if X1 >= X2 then
|
|
% Either empty or singleton segment.
|
|
io.write_int(X2, !IO)
|
|
else
|
|
io.write_int(X1, !IO),
|
|
io.write_char(',', !IO),
|
|
io.write_int(X2, !IO)
|
|
),
|
|
io.write_char(C, !IO),
|
|
io.write_char('\n', !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff_forward_ed takes a diff and displays it in ed(1) format, but
|
|
% with all diff_out forward. This is actually useless for feeding to ed(1),
|
|
% but nicer to read.
|
|
%
|
|
:- pred display_diff_forward_ed(file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_diff_forward_ed(_File1, _File2, [], !IO).
|
|
display_diff_forward_ed(File1, File2, [Cmd | Diff], !IO) :-
|
|
(
|
|
Cmd = add(X, Y1 - Y2),
|
|
write_command_forward_ed(X - X, 'a', !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_string(".\n", !IO)
|
|
;
|
|
Cmd = delete(X, _Y),
|
|
write_command_forward_ed(X, 'd', !IO)
|
|
;
|
|
Cmd = change(X, Y1 - Y2),
|
|
write_command_forward_ed(X, 'c', !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_string(".\n", !IO)
|
|
),
|
|
display_diff_forward_ed(File1, File2, Diff, !IO).
|
|
|
|
% write_command_forward_ed displays a forward ed(1) command.
|
|
% The difference between this and write_command_ed is that the command char
|
|
% comes first here. Who comes up with these dumb formats anyway?
|
|
%
|
|
:- pred write_command_forward_ed(segment::in, char::in, io::di, io::uo) is det.
|
|
|
|
write_command_forward_ed(X - X2, C, !IO) :-
|
|
io.write_char(C, !IO),
|
|
X1 = X + 1, % Convert from pos to line number
|
|
( if X1 >= X2 then
|
|
% Either empty or singleton segment.
|
|
io.write_int(X2, !IO)
|
|
else
|
|
io.write_int(X1, !IO),
|
|
io.write_char(' ', !IO),
|
|
io.write_int(X2, !IO)
|
|
),
|
|
io.write_char('\n', !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff_ifdef writes out the files in a unified diff,
|
|
% using #ifdefs around each edit.
|
|
%
|
|
% TO DO: GNU diff makes this output style much more configurable.
|
|
% We should too.
|
|
%
|
|
:- pred display_diff_ifdef(string::in, file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_diff_ifdef(Sym, File1, File2, Diff, !IO) :-
|
|
display_diff_ifdef_2(0, Sym, File1, File2, Diff, !IO).
|
|
|
|
% Argument 1 (prev) is the last pos displayed before the current edit (or
|
|
% end of edits, in the base case).
|
|
% This is important for when we have to display the "non-diffed" text
|
|
% between edits.
|
|
%
|
|
:- pred display_diff_ifdef_2(int::in, string::in, file::in, file::in,
|
|
diff::in, io::di, io::uo) is det.
|
|
|
|
display_diff_ifdef_2(Prev, _Sym, File1, _File2, [], !IO) :-
|
|
file.get_numlines(File1, SegEnd),
|
|
show_file(File1, "", Prev, SegEnd, !IO).
|
|
display_diff_ifdef_2(Prev, Sym, File1, File2, [Edit | Diff], !IO) :-
|
|
first_mentioned_positions(Edit, StartOfEdit, _),
|
|
show_file(File1, "", Prev, StartOfEdit, !IO),
|
|
(
|
|
Edit = add(X, Y1 - Y2),
|
|
io.write_strings(["#ifdef ", Sym, "\n"], !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_strings(["#endif /* ", Sym, " */\n"], !IO),
|
|
Next = X
|
|
;
|
|
Edit = delete(X1 - X2, _),
|
|
io.write_strings(["#ifndef ", Sym, "\n"], !IO),
|
|
show_file(File1, "", X1, X2, !IO),
|
|
io.write_strings(["#endif /* not ", Sym, " */\n"], !IO),
|
|
Next = X2
|
|
;
|
|
Edit = change(X1 - X2, Y1 - Y2),
|
|
io.write_strings(["#ifndef ", Sym, "\n"], !IO),
|
|
show_file(File1, "", X1, X2, !IO),
|
|
io.write_strings(["#else /* ", Sym, " */\n"], !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_strings(["#endif /* ", Sym, " */\n"], !IO),
|
|
Next = X2
|
|
),
|
|
display_diff_ifdef_2(Next, Sym, File1, File2, Diff, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% display_diff_cvs_merge_conflict writes out the files in a
|
|
% unified diff, using CVS merge conflict marks around each edit.
|
|
%
|
|
:- pred display_diff_cvs_merge_conflict(file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_diff_cvs_merge_conflict(File1, File2, Diff, !IO) :-
|
|
display_diff_cvs_merge_conflict_2(0, File1, File2, Diff, !IO).
|
|
|
|
% Argument 1 (prev) is the last pos displayed before the current edit (or
|
|
% end of edits, in the base case).
|
|
% This is important for when we have to display the "non-diffed" text
|
|
% between edits.
|
|
%
|
|
:- pred display_diff_cvs_merge_conflict_2(int::in, file::in, file::in,
|
|
diff::in, io::di, io::uo) is det.
|
|
|
|
display_diff_cvs_merge_conflict_2(Prev, File1, _File2, [], !IO) :-
|
|
file.get_numlines(File1, SegEnd),
|
|
show_file(File1, "", Prev, SegEnd, !IO).
|
|
display_diff_cvs_merge_conflict_2(Prev, File1, File2, [Edit | Diff], !IO) :-
|
|
first_mentioned_positions(Edit, StartOfEdit, _),
|
|
show_file(File1, "", Prev, StartOfEdit, !IO),
|
|
file.get_file_name(File1, FileName1),
|
|
file.get_file_name(File2, FileName2),
|
|
(
|
|
Edit = add(X, Y1 - Y2),
|
|
io.write_strings(["<<<<<<< ", FileName1, "\n"], !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_string("=======\n", !IO),
|
|
io.write_strings([">>>>>>> ", FileName2, "\n"], !IO),
|
|
Next = X
|
|
;
|
|
Edit = delete(X1 - X2, _),
|
|
io.write_strings(["<<<<<<< ", FileName1, "\n"], !IO),
|
|
io.write_string("=======\n", !IO),
|
|
show_file(File1, "", X1, X2, !IO),
|
|
io.write_strings([">>>>>>> ", FileName2, "\n"], !IO),
|
|
Next = X2
|
|
;
|
|
Edit = change(X1 - X2, Y1 - Y2),
|
|
io.write_strings(["<<<<<<< ", FileName1, "\n"], !IO),
|
|
show_file(File1, "", X1, X2, !IO),
|
|
io.write_string("=======\n", !IO),
|
|
show_file(File2, "", Y1, Y2, !IO),
|
|
io.write_strings([">>>>>>> ", FileName2, "\n"], !IO),
|
|
Next = X2
|
|
),
|
|
display_diff_cvs_merge_conflict_2(Next, File1, File2, Diff, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Types for context/unified diffs.
|
|
|
|
% A context diff is a bit more complicated than a "standard"
|
|
% diff, because it requires the display of some parts of the
|
|
% files which are not actually part of the diff, but not all
|
|
% of it.
|
|
%
|
|
% Because context and unified diffs both require the same
|
|
% kind of information, we factor out the code to turn a
|
|
% normal diff into a context diff.
|
|
%
|
|
:- type context_edit
|
|
---> context_edit(segment, segment, diff).
|
|
|
|
:- type context_diff == list(context_edit).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred diff_to_context_diff(int::in, int::in, int::in, diff::in,
|
|
context_diff::out) is det.
|
|
|
|
diff_to_context_diff(_Xsize, _Ysize, _Context, [], []).
|
|
diff_to_context_diff(Xsize, Ysize, Context, [Edit | Diff], CDiff) :-
|
|
diff_to_context_diff(Xsize, Ysize, Context, Diff, CDiff0),
|
|
|
|
% Work out how far the context of this edit reaches.
|
|
first_mentioned_positions(Edit, Xfirst0, Yfirst0),
|
|
int.max(Xfirst0 - Context, 0, Xfirst),
|
|
int.max(Yfirst0 - Context, 0, Yfirst),
|
|
last_mentioned_positions(Edit, Xlast0, Ylast0),
|
|
int.min(Xlast0 + Context, Xsize, Xlast),
|
|
int.min(Ylast0 + Context, Ysize, Ylast),
|
|
|
|
(
|
|
CDiff0 = [],
|
|
CDiff = [context_edit(Xfirst - Xlast, Yfirst - Ylast, [Edit])]
|
|
;
|
|
CDiff0 =
|
|
[context_edit(XsegLo - XsegHi, YsegLo - YsegHi, DDiff) | CDiff1],
|
|
% Should we merge this edit into the next one?
|
|
( if
|
|
( XsegLo =< Xlast
|
|
; YsegLo =< Ylast
|
|
)
|
|
then
|
|
CDiff = [context_edit(Xfirst - XsegHi, Yfirst - YsegHi, [Edit
|
|
| DDiff]) | CDiff1]
|
|
else
|
|
CDiff = [context_edit(Xfirst - Xlast, Yfirst - Ylast,
|
|
[Edit]) | CDiff0]
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Display a diff in unified format.
|
|
%
|
|
:- pred display_unified_diff(int::in, file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_unified_diff(Context, File1, File2, Diff, !IO) :-
|
|
file.get_numlines(File1, Size1),
|
|
file.get_numlines(File2, Size2),
|
|
diff_to_context_diff(Size1, Size2, Context, Diff, CDiff),
|
|
file.get_file_name(File1, Name1),
|
|
file.get_file_name(File2, Name2),
|
|
% XXX Should also print out file dates. But how?
|
|
io.write_strings(["--- ", Name1, "\n"], !IO),
|
|
io.write_strings(["+++ ", Name2, "\n"], !IO),
|
|
globals.io_lookup_bool_option(initial_tab, InitialTab, !IO),
|
|
(
|
|
InitialTab = no,
|
|
NoneStr = " ",
|
|
AddStr = "+",
|
|
DelStr = "-"
|
|
;
|
|
InitialTab = yes,
|
|
NoneStr = "\t",
|
|
AddStr = "+\t",
|
|
DelStr = "-\t"
|
|
),
|
|
display_unified_diff_2(File1, File2, CDiff, NoneStr, AddStr, DelStr, !IO).
|
|
|
|
:- pred display_unified_diff_2(file::in, file::in, context_diff::in,
|
|
string::in, string::in, string::in, io::di, io::uo) is det.
|
|
|
|
display_unified_diff_2(_File1, _File2, [], _, _, _, !IO).
|
|
display_unified_diff_2(File1, File2, [Edit | CDiff], NoneStr, AddStr, DelStr,
|
|
!IO) :-
|
|
Edit = context_edit(Xlow - Xhigh, Ylow - Yhigh, Diff),
|
|
io.format("@@ -%d,%d +%d,%d @@\n",
|
|
[i(Xlow + 1), i(Xhigh - Xlow), i(Ylow + 1), i(Yhigh - Ylow)], !IO),
|
|
display_unified_diff_3(Xlow, Xhigh, File1, File2, Diff, NoneStr, AddStr,
|
|
DelStr, !IO),
|
|
display_unified_diff_2(File1, File2, CDiff, NoneStr, AddStr, DelStr, !IO).
|
|
|
|
:- pred display_unified_diff_3(int::in, int::in, file::in, file::in, diff::in,
|
|
string::in, string::in, string::in, io::di, io::uo) is det.
|
|
|
|
display_unified_diff_3(Prev, Size1, File1, _File2, [], NoneStr, _, _, !IO) :-
|
|
show_file(File1, NoneStr, Prev, Size1, !IO).
|
|
display_unified_diff_3(Prev, Size1, File1, File2, [Edit | Diff],
|
|
NoneStr, AddStr, DelStr, !IO) :-
|
|
first_mentioned_positions(Edit, StartOfEdit, _),
|
|
show_file(File1, NoneStr, Prev, StartOfEdit, !IO),
|
|
(
|
|
Edit = add(X, Y1 - Y2),
|
|
show_file(File2, AddStr, Y1, Y2, !IO),
|
|
Next = X
|
|
;
|
|
Edit = delete(X1 - X2, _),
|
|
show_file(File1, DelStr, X1, X2, !IO),
|
|
Next = X1
|
|
;
|
|
Edit = change(X1 - X2, Y1 - Y2),
|
|
show_file(File1, DelStr, X1, X2, !IO),
|
|
show_file(File2, AddStr, Y1, Y2, !IO),
|
|
Next = X1
|
|
),
|
|
display_unified_diff_3(Next, Size1, File1, File2, Diff,
|
|
NoneStr, AddStr, DelStr, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Display a diff in context format.
|
|
%
|
|
:- pred display_context_diff(int::in, file::in, file::in, diff::in,
|
|
io::di, io::uo) is det.
|
|
|
|
display_context_diff(Context, File1, File2, Diff, !IO) :-
|
|
file.get_numlines(File1, Size1),
|
|
file.get_numlines(File2, Size2),
|
|
diff_to_context_diff(Size1, Size2, Context, Diff, CDiff),
|
|
file.get_file_name(File1, Name1),
|
|
file.get_file_name(File2, Name2),
|
|
% XXX Should also print out file dates. But how??
|
|
io.write_strings(["*** ", Name1, "\n"], !IO),
|
|
io.write_strings(["--- ", Name2, "\n"], !IO),
|
|
|
|
globals.io_lookup_bool_option(initial_tab, InitialTab, !IO),
|
|
(
|
|
InitialTab = no,
|
|
NoneStr = " ",
|
|
AddStr = "+ ",
|
|
DelStr = "- ",
|
|
ChgStr = "! "
|
|
;
|
|
InitialTab = yes,
|
|
NoneStr = "\t",
|
|
AddStr = "+\t",
|
|
DelStr = "-\t",
|
|
ChgStr = "!\t"
|
|
),
|
|
display_context_diff_2(File1, File2, CDiff, NoneStr, AddStr, DelStr,
|
|
ChgStr, !IO).
|
|
|
|
:- pred display_context_diff_2(file::in, file::in, context_diff::in,
|
|
string::in, string::in, string::in, string::in, io::di, io::uo) is det.
|
|
|
|
display_context_diff_2(_File1, _File2, [], _, _, _, _, !IO).
|
|
display_context_diff_2(File1, File2, [Edit | CDiff],
|
|
NoneStr, AddStr, DelStr, ChgStr, !IO) :-
|
|
Edit = context_edit(Xlow - Xhigh, Ylow - Yhigh, Diff),
|
|
io.write_string("***************\n", !IO),
|
|
io.format("*** %d,%d ****\n", [i(Xlow + 1), i(Xhigh)], !IO),
|
|
|
|
% Don't display the "context from" lines if there's nothing deleted or
|
|
% changed.
|
|
%
|
|
( if
|
|
all [AEdit] list.member(AEdit, Diff) => AEdit = add(_, _)
|
|
then
|
|
true
|
|
else
|
|
display_context_diff_left(Xlow, Xhigh, File1, Diff, NoneStr,
|
|
DelStr, ChgStr, !IO)
|
|
),
|
|
io.format("--- %d,%d ----\n", [i(Ylow + 1), i(Yhigh)], !IO),
|
|
|
|
% Don't display the "context to" lines if there's nothing added or changed.
|
|
%
|
|
( if
|
|
all [DEdit] list.member(DEdit, Diff) => DEdit = delete(_, _)
|
|
then
|
|
true
|
|
else
|
|
display_context_diff_right(Ylow, Yhigh, File2, Diff, NoneStr,
|
|
AddStr, ChgStr, !IO)
|
|
),
|
|
display_context_diff_2(File1, File2, CDiff, NoneStr, AddStr, DelStr,
|
|
ChgStr, !IO).
|
|
|
|
:- pred display_context_diff_left(int::in, int::in, file::in, diff::in,
|
|
string::in, string::in, string::in, io::di, io::uo) is det.
|
|
|
|
display_context_diff_left(Prev, Size1, File1, [], NoneStr, _, _, !IO) :-
|
|
show_file(File1, NoneStr, Prev, Size1, !IO).
|
|
display_context_diff_left(Prev, Size1, File1, [Edit | Diff], NoneStr,
|
|
DelStr, ChgStr, !IO) :-
|
|
first_mentioned_positions(Edit, StartOfEdit, _),
|
|
show_file(File1, NoneStr, Prev, StartOfEdit, !IO),
|
|
(
|
|
Edit = add(X, _),
|
|
Next = X
|
|
;
|
|
Edit = delete(X1 - X2, _),
|
|
show_file(File1, DelStr, X1, X2, !IO),
|
|
Next = X2
|
|
;
|
|
Edit = change(X1 - X2, _),
|
|
show_file(File1, ChgStr, X1, X2, !IO),
|
|
Next = X2
|
|
),
|
|
display_context_diff_left(Next, Size1, File1, Diff, NoneStr,
|
|
DelStr, ChgStr, !IO).
|
|
|
|
:- pred display_context_diff_right(int::in, int::in, file::in, diff::in,
|
|
string::in, string::in, string::in, io::di, io::uo) is det.
|
|
|
|
display_context_diff_right(Prev, Size2, File2, [], NoneStr, _, _, !IO) :-
|
|
diff_out.show_file(File2, NoneStr, Prev, Size2, !IO).
|
|
display_context_diff_right(Prev, Size2, File2, [Edit | Diff], NoneStr,
|
|
AddStr, ChgStr, !IO) :-
|
|
first_mentioned_positions(Edit, StartOfEdit, _),
|
|
show_file(File2, NoneStr, Prev, StartOfEdit, !IO),
|
|
(
|
|
Edit = add(_, Y1 - Y2),
|
|
show_file(File2, AddStr, Y1, Y2, !IO),
|
|
Next = Y2
|
|
;
|
|
Edit = delete(_, Y),
|
|
Next = Y
|
|
;
|
|
Edit = change(_, Y1 - Y2),
|
|
show_file(File2, ChgStr, Y1, Y2, !IO),
|
|
Next = Y2
|
|
),
|
|
display_context_diff_right(Next, Size2, File2, Diff, NoneStr, AddStr,
|
|
ChgStr, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Side-by-side diffs are incredibly complex, as you'll see if
|
|
% you inspect the code below.
|
|
%
|
|
% TO DO: GNU diff has --sdiff-merge-assist, but I can find no documentation
|
|
% on what this actually does, and haven't had the time to investigate.
|
|
% For the moment, we accept the option and note here whether or not
|
|
% it is turned on, but do nothing with it.
|
|
|
|
% Parameters to pass around.
|
|
%
|
|
:- type side_by_side_info
|
|
---> side_by_side_info(
|
|
int, % Half width
|
|
int, % Column 2 offset
|
|
bool, % Left column only
|
|
bool, % Suppress common lines
|
|
bool % Help sdiff
|
|
).
|
|
|
|
:- pred display_diff_side_by_side(file::in, file::in, diff::in, io::di, io::uo)
|
|
is det.
|
|
|
|
display_diff_side_by_side(File1, File2, Diff, !IO) :-
|
|
globals.io_lookup_int_option(width, Width0, !IO),
|
|
|
|
% Calculate the half-width and offset stuff.
|
|
|
|
% XXX If we're expanding tabs, we should
|
|
% factor this in.
|
|
Off = (Width0 + 4) // 8 * 4,
|
|
Max = Off - 3,
|
|
HalfWidth0 = Width0 - Off + 1,
|
|
( if HalfWidth0 =< 0 then
|
|
HalfWidth = 0
|
|
else if HalfWidth0 > Max then
|
|
HalfWidth = Max
|
|
else
|
|
HalfWidth = HalfWidth0
|
|
),
|
|
( if HalfWidth > 0 then
|
|
Col2Off = Off
|
|
else
|
|
Col2Off = Width0
|
|
),
|
|
globals.io_lookup_bool_option(left_column, LeftCol, !IO),
|
|
globals.io_lookup_bool_option(suppress_common_lines, Suppress, !IO),
|
|
globals.io_lookup_bool_option(sdiff_merge_assist, Sdiff, !IO),
|
|
SBS = side_by_side_info(HalfWidth, Col2Off, LeftCol, Suppress, Sdiff),
|
|
display_diff_side_by_side_2(0, SBS, File1, File2, Diff, !IO).
|
|
|
|
:- pred display_diff_side_by_side_2(int::in, side_by_side_info::in,
|
|
file::in, file::in, diff::in, io::di, io::uo) is det.
|
|
|
|
display_diff_side_by_side_2(Prev, SBS, File1, _File2, [], !IO) :-
|
|
SBS = side_by_side_info(_, _, _, Suppress, _),
|
|
(
|
|
Suppress = no,
|
|
file.get_numlines(File1, SegEnd),
|
|
show_sbs_same_lines(File1, SBS, Prev - SegEnd, !IO)
|
|
;
|
|
Suppress = yes
|
|
).
|
|
display_diff_side_by_side_2(Prev, SBS, File1, File2, [Edit | Diff], !IO) :-
|
|
SBS = side_by_side_info(_, _, _, Suppress, _),
|
|
first_mentioned_positions(Edit, StartOfEdit, _),
|
|
(
|
|
Suppress = no,
|
|
show_sbs_same_lines(File1, SBS, Prev - StartOfEdit, !IO)
|
|
;
|
|
Suppress = yes
|
|
),
|
|
(
|
|
Edit = add(X, Seg2),
|
|
show_sbs_added_lines(File2, SBS, Seg2, !IO),
|
|
Next = X
|
|
;
|
|
Edit = delete(X1 - X2, _),
|
|
show_sbs_deleted_lines(File1, SBS, X1 - X2, !IO),
|
|
Next = X2
|
|
;
|
|
Edit = change(X1 - X2, Y1 - Y2),
|
|
% The side-by-side change diff format is sort of weird.
|
|
% We have to compute the minimum of the two change sizes, and display
|
|
% "changed" lines for the minimum of these sizes. Then we display
|
|
% "added" or "deleted" lines for whatever is left over.
|
|
int.min(X2 - X1, Y2 - Y1, Size),
|
|
show_sbs_changed_lines(File1, File2, SBS, X1, Y1, Size, !IO),
|
|
show_sbs_deleted_lines(File1, SBS, (X1 + Size) - X2, !IO),
|
|
show_sbs_added_lines(File2, SBS, (Y1 + Size) - Y2, !IO),
|
|
Next = X2
|
|
),
|
|
display_diff_side_by_side_2(Next, SBS, File1, File2, Diff, !IO).
|
|
|
|
:- pred show_sbs_changed_lines(file::in, file::in, side_by_side_info::in,
|
|
int::in, int::in, int::in, io::di, io::uo) is det.
|
|
|
|
show_sbs_changed_lines(File1, File2, SBS, X1, Y1, Size, !IO) :-
|
|
( if Size > 0 then
|
|
( if
|
|
file.get_line(File1, X1, Line1),
|
|
file.get_line(File2, Y1, Line2)
|
|
then
|
|
SBS = side_by_side_info(Width, _, _, _, _),
|
|
string.to_char_list(Line1, Chars1),
|
|
print_half_line(Chars1, SBS, 0, 0, Width, OutPos, !IO),
|
|
tab_to_column(OutPos, Width, !IO),
|
|
io.write_string("|", !IO),
|
|
tab_to_column(Width + 1, Width + 2, !IO),
|
|
string.to_char_list(Line2, Chars2),
|
|
print_half_line(Chars2, SBS, 0, 0, Width, _, !IO),
|
|
io.write_string("\n", !IO),
|
|
show_sbs_changed_lines(File1, File2, SBS, X1 + 1, Y1 + 1,
|
|
Size - 1, !IO)
|
|
else
|
|
error("show_sbs_changed_lines: file ended prematurely")
|
|
)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred show_sbs_same_lines(file::in, side_by_side_info::in, segment::in,
|
|
io::di, io::uo) is det.
|
|
|
|
show_sbs_same_lines(File, SBS, Low - High, !IO) :-
|
|
( if Low < High then
|
|
( if file.get_line(File, Low, Line) then
|
|
SBS = side_by_side_info(Width, _, LeftCol, _, _),
|
|
string.to_char_list(Line, Chars),
|
|
print_half_line(Chars, SBS, 0, 0, Width, OutPos, !IO),
|
|
|
|
% If the user specified --left, don't display the right column
|
|
% here.
|
|
%
|
|
(
|
|
LeftCol = yes,
|
|
tab_to_column(OutPos, Width, !IO),
|
|
io.write_string("(", !IO)
|
|
;
|
|
LeftCol = no,
|
|
tab_to_column(OutPos, Width + 2, !IO),
|
|
print_half_line(Chars, SBS, 0, 0, Width, _, !IO)
|
|
),
|
|
io.write_string("\n", !IO),
|
|
show_sbs_same_lines(File, SBS, (Low + 1) - High, !IO)
|
|
else
|
|
error("show_sbs_same_lines: file ended prematurely")
|
|
)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred show_sbs_added_lines(file::in, side_by_side_info::in,
|
|
segment::in, io::di, io::uo) is det.
|
|
|
|
show_sbs_added_lines(File, SBS, Low - High, !IO) :-
|
|
( if Low < High then
|
|
( if file.get_line(File, Low, Line) then
|
|
SBS = side_by_side_info(Width, _, _, _, _),
|
|
string.to_char_list(Line, Chars),
|
|
tab_to_column(0, Width, !IO),
|
|
io.write_string("> ", !IO),
|
|
print_half_line(Chars, SBS, 0, 0, Width, _, !IO),
|
|
io.write_string("\n", !IO),
|
|
show_sbs_added_lines(File, SBS, (Low + 1) - High, !IO)
|
|
else
|
|
error("show_sbs_added_lines: file ended prematurely")
|
|
)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred show_sbs_deleted_lines(file::in, side_by_side_info::in,
|
|
segment::in, io::di, io::uo) is det.
|
|
|
|
show_sbs_deleted_lines(File, SBS, Low - High, !IO) :-
|
|
( if Low < High then
|
|
( if file.get_line(File, Low, Line) then
|
|
SBS = side_by_side_info(Width, _, _, _, _),
|
|
string.to_char_list(Line, Chars),
|
|
print_half_line(Chars, SBS, 0, 0, Width, OutPos, !IO),
|
|
tab_to_column(OutPos, Width, !IO),
|
|
io.write_string("<\n", !IO),
|
|
show_sbs_deleted_lines(File, SBS, (Low + 1) - High, !IO)
|
|
else
|
|
error("show_sbs_deleted_lines: file ended prematurely")
|
|
)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- func tab_width = int.
|
|
|
|
tab_width = 8.
|
|
|
|
% Put a number of spaces on the output stream.
|
|
% Update % the output column as we go.
|
|
%
|
|
:- pred put_spaces(int::in, int::in, int::out, io::di, io::uo) is det.
|
|
|
|
put_spaces(Spaces, !OutPos, !IO) :-
|
|
( if Spaces =< 0 then
|
|
true
|
|
else
|
|
io.write_char(' ', !IO),
|
|
!:OutPos = !.OutPos + 1,
|
|
put_spaces(Spaces - 1, !OutPos, !IO)
|
|
).
|
|
|
|
% Given a "from" column and a "to" column, put sufficient spaces
|
|
% on the output stream to reach that column. Use tabs if we can.
|
|
%
|
|
:- pred tab_to_column(int::in, int::in, io::di, io::uo) is det.
|
|
|
|
tab_to_column(From, To, !IO) :-
|
|
AfterTab = From + tab_width - (From rem tab_width),
|
|
( if AfterTab > To then
|
|
( if From < To then
|
|
io.write_char(' ', !IO),
|
|
tab_to_column(From + 1, To, !IO)
|
|
else
|
|
true
|
|
)
|
|
else
|
|
io.write_char('\t', !IO),
|
|
tab_to_column(AfterTab, To, !IO)
|
|
).
|
|
|
|
% Display half a line in a side-by-side diff, stopping when
|
|
% we reach a certain column.
|
|
%
|
|
% This is actually a very simple thing to do, except for one
|
|
% complication, which is the displaying of tab characters.
|
|
%
|
|
% The important variables are:
|
|
%
|
|
% InPos: The current column in the input line.
|
|
% OutPos: The current column in the output line.
|
|
% OutBound: The column that we must stop at.
|
|
%
|
|
:- pred print_half_line(list(char)::in, side_by_side_info::in,
|
|
int::in, int::in, int::in, int::out,
|
|
io::di, io::uo) is det.
|
|
|
|
print_half_line([], _SBS, _InPos, OutPos, _OutBound, OutPos, !IO).
|
|
print_half_line([C | Cs], SBS, InPos0, OutPos0, OutBound, OutPos, !IO) :-
|
|
( if
|
|
C = '\t'
|
|
then
|
|
% Calculate how many spaces this tab is worth.
|
|
Spaces = tab_width - InPos0 rem tab_width,
|
|
( if InPos0 = OutPos0 then
|
|
globals.io_lookup_bool_option(expand_tabs, ExpandTabs, !IO),
|
|
(
|
|
ExpandTabs = yes,
|
|
% If we're expanding tabs, we just pretend that
|
|
% we had Spaces spaces and write them.
|
|
TabStop0 = OutPos0 + Spaces,
|
|
( if TabStop0 > OutBound then
|
|
TabStop = OutBound
|
|
else
|
|
TabStop = TabStop0
|
|
),
|
|
put_spaces(TabStop - OutPos0, OutPos0, OutPos1, !IO)
|
|
;
|
|
% If we're not expanding tabs, just print it and
|
|
% hope everything lines up okay.
|
|
ExpandTabs = no,
|
|
io.write_char('\t', !IO),
|
|
OutPos1 = OutPos0 + Spaces
|
|
)
|
|
else
|
|
OutPos1 = OutPos0
|
|
),
|
|
InPos = InPos0 + Spaces
|
|
else if
|
|
( C = '\r' ; C = '\b' ; C = '\n' )
|
|
then
|
|
% XXX What to do? For the moment, we'll just ignore it.
|
|
InPos = InPos0, OutPos1 = OutPos0
|
|
/***********
|
|
% XXX Binary files aren't really supported.
|
|
else if
|
|
not char.is_print(C)
|
|
then
|
|
InPos = InPos0, OutPos1 = OutPos0
|
|
( if InPos < OutBound then
|
|
io.write_char(C, !IO)
|
|
else
|
|
true
|
|
)
|
|
***********/
|
|
else
|
|
% The default case. Print and be done with it.
|
|
InPos = InPos0 + 1,
|
|
( if InPos < OutBound then
|
|
OutPos1 = InPos,
|
|
io.write_char(C, !IO)
|
|
else
|
|
OutPos1 = OutPos0
|
|
)
|
|
),
|
|
print_half_line(Cs, SBS, InPos, OutPos1, OutBound, OutPos, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module diff_out.
|
|
%-----------------------------------------------------------------------------%
|