Files
mercury/samples/diff/diff_out.m
Zoltan Somogyi a653024ab7 Update many aspects of style in sample programs.
If we want to encourage people to read the sample programs
and learn Mercury programming from them, they should not be written
in an obsolete style.

samples/beer.m:
samples/calculator.m:
samples/calculator2.m:
samples/concurrency/midimon/midimon.m:
samples/diff/diff_out.m:
samples/e.m:
samples/eliza.m:
samples/muz/dict.m:
samples/muz/higher_order.m:
samples/muz/muz.m:
samples/muz/typecheck.m:
samples/muz/word.m:
samples/muz/zabstract.m:
samples/muz/zlogic.m:
samples/muz/zparser.m:
samples/muz/ztoken.m:
samples/muz/ztoken_io.m:
samples/muz/ztype.m:
samples/muz/ztype_op.m:
samples/rot13/rot13_concise.m:
samples/rot13/rot13_gustavo.m:
samples/rot13/rot13_juergen.m:
samples/rot13/rot13_ralph.m:
samples/rot13/rot13_verbose.m:
samples/solutions/all_solutions.m:
samples/solutions/n_solutions.m:
samples/solutions/one_solution.m:
samples/solutions/some_solutions.m:
samples/solver_types/eqneq.m:
samples/solver_types/sudoku.m:
samples/solver_types/test_eqneq.m:
    Replace uses of __ as module qualifier with dot.

    Replace (C->T;E) with (if C then T else E).

    Use our usual indentation for if-then-elses and for switches.

    Import one module per line. Put those imports into alphabetical order.

    Replace many uses of DCGs with state variables, leaving DCGs
    mostly just for parsing code.

    Use predmode declarations where this helps.

    Put predicates in top-down order where relevant.

    Use io.format where this helps.

    Do not put more than one predicate call on one line.

    Put each function symbol in a du type on a separate line.

    Put spaces after commas, around the bar in list syntax,
    around arithmetic operators, and around minus signs used for pairs.

    Replace tab indentation with four-space indentation.

    Delete spaces at the ends of lines.
    Replace two or more consecutive blank lines with one blank line.
    Delete blank lines that do not help structure the code.

    There are probably still some examples of old practices remaining;
    I do not claim to have fixed them all.
2021-07-07 05:32:09 +10:00

1074 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 are 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 are 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 are 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.
%-----------------------------------------------------------------------------%