1082 lines
29 KiB
Prolog
1082 lines
29 KiB
Prolog
/* $Id$
|
|
|
|
Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: jan@swi.psy.uva.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 1985-2002, University of Amsterdam
|
|
|
|
This program 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 2
|
|
of the License, 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 Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
As a special exception, if you link this library with other files,
|
|
compiled with a Free Software compiler, to produce an executable, this
|
|
library does not by itself cause the resulting executable to be covered
|
|
by the GNU General Public License. This exception does not however
|
|
invalidate any other reasons why the executable file might be covered by
|
|
the GNU General Public License.
|
|
*/
|
|
|
|
:- module(emacs_sgml_mode, []).
|
|
:- use_module(library(pce)).
|
|
:- use_module(library(emacs_extend)).
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
This module exploits the SGML/XML parser from the SWI-Prolog package
|
|
sgml2pl to provide syntax colouring for SGML, XML and HTML modes. Based
|
|
on a true parser, we can provide much better feedback as heuristic
|
|
parsers used in most syntax-driven editors. For example, we can provide
|
|
feedback on SHORTREF matches in SGML mode by highlighting the tokens
|
|
acting as a short reference. We can also easily give the scope of
|
|
elements that are closed by omited elements.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
:- emacs_begin_mode(sgml, language,
|
|
"Mode for editing SGML documents",
|
|
% BINDINGS
|
|
[ open_document = button(sgml),
|
|
reload_dtd = button(sgml),
|
|
colourise_buffer = button(sgml),
|
|
colourise_and_recenter = key('\\C-l'),
|
|
tag_selection = key('\\e,'),
|
|
colourise_element = key('\\C-c\\C-s'),
|
|
forward_move_out = key('\\ee')
|
|
],
|
|
% SYNTAX TABLE
|
|
[ '"' = string_quote('"'),
|
|
'\'' = string_quote('\''),
|
|
paragraph_end('\\s *$\\|^<p>\\|\\s +<')
|
|
]).
|
|
|
|
class_variable(auto_colourise_size_limit, int, 100000,
|
|
"Auto-colourise if buffer is smaller then this").
|
|
|
|
variable(dialect,
|
|
{sgml,xml,html}:=sgml,
|
|
both,
|
|
"?ML Dialect used to parse").
|
|
variable(upcase_elements,
|
|
bool := @off,
|
|
both,
|
|
"Upcase inserted elements?").
|
|
variable(parser,
|
|
prolog,
|
|
none,
|
|
"Associated (DTD) parser").
|
|
variable(auto_colourise_size_limit,
|
|
int,
|
|
both,
|
|
"Auto-colourise if buffer is smaller then this").
|
|
|
|
% make_parser(M, Parser)
|
|
%
|
|
% Create a parser suitable for the current mode and load the DTD
|
|
% into it.
|
|
|
|
make_parser(M, Parser) :-
|
|
get(M, dialect, Dialect),
|
|
get(M, text_buffer, TB),
|
|
get(TB, file, File),
|
|
get(File, name, FileName),
|
|
get(M, dialect, Dialect),
|
|
( Dialect == html
|
|
-> TheDialect = sgml,
|
|
dtd(html, DTD),
|
|
Options = [dtd(DTD)]
|
|
; TheDialect = Dialect,
|
|
Options = []
|
|
),
|
|
new_sgml_parser(Parser, Options),
|
|
set_sgml_parser(Parser, file(FileName)),
|
|
set_sgml_parser(Parser, dialect(TheDialect)).
|
|
|
|
|
|
% load_dtd(+Mode, +Parser)
|
|
%
|
|
% Load the document DTD into the given parser.
|
|
|
|
load_dtd(M, _) :-
|
|
get(M, dialect, html), !.
|
|
load_dtd(M, Parser) :-
|
|
get(M, text_buffer, TB),
|
|
new(Re, regex('<!DOCTYPE', @off)),
|
|
( send(Re, search, TB)
|
|
-> get(Re, register_start, Start),
|
|
pce_open(TB, read, In),
|
|
seek(In, Start, bof, _),
|
|
catch(sgml_parse(Parser,
|
|
[ source(In),
|
|
parse(declaration)
|
|
]),
|
|
E,
|
|
show_message(M, E)),
|
|
close(In)
|
|
; send(M, report, warning, 'No <!DOCTYPE declaration')
|
|
).
|
|
|
|
|
|
parser(M, Reload:[bool], Parser:prolog) :<-
|
|
"Fetch the default parser"::
|
|
( Reload \== @on,
|
|
get(M, slot, parser, Parser),
|
|
Parser = sgml_parser(_)
|
|
-> true
|
|
; send(M, destroy_dtd),
|
|
make_parser(M, Parser),
|
|
load_dtd(M, Parser),
|
|
send(M, slot, parser, Parser),
|
|
set_sgml_parser(Parser, doctype(_)) % use for partial parsing
|
|
).
|
|
|
|
|
|
dtd(M, Reload:[bool], DTD:prolog) :<-
|
|
"Fetch the current DTD"::
|
|
get(M, parser, Reload, Parser),
|
|
get_sgml_parser(Parser, dtd(DTD)).
|
|
|
|
|
|
reload_dtd(M) :->
|
|
"Reload the DTD"::
|
|
get(M, dtd, _).
|
|
|
|
|
|
destroy_dtd(M) :->
|
|
"Destroy the associated DTD object"::
|
|
( get(M, slot, parser, Parser),
|
|
Parser = sgml_parser(_)
|
|
-> free_sgml_parser(Parser),
|
|
send(M, slot, parser, [])
|
|
; true
|
|
).
|
|
|
|
|
|
/*******************************
|
|
* INITIALISE *
|
|
*******************************/
|
|
|
|
setup_mode(E) :->
|
|
"Switch editor into fill-mode"::
|
|
send(E, fill_mode, @on).
|
|
|
|
|
|
unlink(M) :->
|
|
send(M, destroy_dtd),
|
|
send_super(M, unlink).
|
|
|
|
|
|
open_document(M, DT:doctype=name) :->
|
|
"Insert <!DOCTYPE line"::
|
|
send(M, format, '<!DOCTYPE %s SYSTEM "">\n\n', DT),
|
|
send(M, backward_char, 4).
|
|
|
|
|
|
/*******************************
|
|
* HELP *
|
|
*******************************/
|
|
|
|
help_on_mode(M) :->
|
|
( absolute_file_name(sgml_mode,
|
|
[ extensions([html]),
|
|
access(read),
|
|
file_errors(fail)
|
|
],
|
|
HTML)
|
|
-> atom_concat('file:', HTML, URI),
|
|
www_open_url(URI)
|
|
; send(M, report, warning, 'Can''t find help file')
|
|
).
|
|
|
|
/*******************************
|
|
* RECOULOR POLICIES *
|
|
*******************************/
|
|
|
|
idle(M) :->
|
|
"Idle event was received, colour the current element"::
|
|
send(M, colourise_element, @off).
|
|
|
|
setup_styles(M) :->
|
|
"Associate defined syntax-styles"::
|
|
( get(M, attribute, styles_initialised, @on)
|
|
-> true
|
|
; send(M, reload_styles),
|
|
send(M, attribute, styles_initialised, @on)
|
|
).
|
|
|
|
set_caret_and_inform(M) :->
|
|
get(M, editor, Editor),
|
|
get(Editor?image, index, @event, Caret),
|
|
send(M, caret, Caret),
|
|
get(M?text_buffer, find_all_fragments,
|
|
message(@arg1, overlap, Caret),
|
|
Fragments),
|
|
send(Fragments, sort, ?(@arg1?length, compare, @arg2?length)),
|
|
get(Fragments, find, ?(@arg1, attribute, balloon), Frag),
|
|
get(Frag, balloon, Balloon),
|
|
send(M, report, warning, 'SGML warning: %s', Balloon).
|
|
|
|
event(M, Ev:event) :->
|
|
"Show insert-menu on right-down"::
|
|
send(Ev, is_a, ms_right_down),
|
|
( get(M?image, index, Ev, I)
|
|
-> send(M, caret, I)
|
|
; true
|
|
),
|
|
send(M, show_menu, Ev).
|
|
|
|
reload_styles(M) :->
|
|
"Force reloading the styles"::
|
|
( style_object(Name, Style),
|
|
send(M, style, Name, Style),
|
|
fail
|
|
; true
|
|
).
|
|
|
|
colourise_element(M, Warn:[bool]) :->
|
|
"Colour element at location"::
|
|
send(M, setup_styles),
|
|
get(M, caret, Caret),
|
|
get(M, text_buffer, TB),
|
|
new(Re, regex('<\\w+')),
|
|
make_parser(M, Parser),
|
|
load_dtd(M, Parser),
|
|
set_sgml_parser(Parser, doctype(_)),
|
|
pce_open(TB, read, In),
|
|
( get(TB, scan, Caret, line, -2, start, Start),
|
|
% format('Starting from ~w~n', [Start]),
|
|
find_element(M, Parser, Re, In, Start, From-To),
|
|
Caret < To
|
|
-> send(M, remove_syntax_fragments, From, To),
|
|
% colour_item(element, TB, From, To),
|
|
seek(In, From, bof, _),
|
|
set_sgml_parser(Parser, charpos(From)),
|
|
colourise(M, Parser,
|
|
[ source(In),
|
|
parse(element)
|
|
])
|
|
; Warn == @off
|
|
-> true
|
|
; send(M, report, warning, 'Could not find element')
|
|
),
|
|
close(In),
|
|
free_sgml_parser(Parser).
|
|
|
|
% find_element(+Mode, +Parser, +BeginRegex, +In, +Caret, -From-To)
|
|
%
|
|
% Find the start and end of the current element. We do so by scanning
|
|
% backwards to '<\\w+' (Re). Then we parse the element and see where
|
|
% it ends. If this isn't passed the current caret location we look
|
|
% further backward.
|
|
%
|
|
% This predicate is non-deterministic, broadening the scope on
|
|
% backtracking.
|
|
%
|
|
% By asserting caret/1 before calling this predicate, it will
|
|
% assert a term element(Tag, Attributes, Start, End), where both
|
|
% locations are terms of the form loc(Class, Start, End) indicating
|
|
% the location and type of the begin- and end-tag.
|
|
|
|
:- dynamic
|
|
caret/1, % Caret
|
|
element/4, % Tag, Attributes, Start, End
|
|
stack/5. % Tag, Attributes, Class, Fro, To
|
|
|
|
set_caret(Caret) :-
|
|
retractall(caret(Caret)),
|
|
assert(caret(Caret)).
|
|
|
|
unset_caret :-
|
|
retractall(caret(_)).
|
|
|
|
find_element(M, Caret, Range) :-
|
|
get(M, parser, Parser),
|
|
get(M, text_buffer, TB),
|
|
pce_open(TB, read, In),
|
|
new(Re, regex('<\\w+')),
|
|
( find_element(M, Parser, Re, In, Caret, Range)
|
|
-> close(In)
|
|
; close(In),
|
|
fail
|
|
).
|
|
|
|
find_element(M, Parser, Re, In, Caret, Range) :-
|
|
get(M, text_buffer, TB),
|
|
send(Re, search, TB, Caret, 0),
|
|
get(Re, register_start, 0, Start0),
|
|
find_element(M, Parser, Re, In, Caret, Start0, Range).
|
|
|
|
find_element(M, Parser, _Re, In, Caret, Start, Start-To) :-
|
|
\+ get(M?text_buffer, find_fragment,
|
|
and(message(@arg1, overlap, Start),
|
|
@arg1?parsed == @off),
|
|
_),
|
|
seek(In, Start, bof, _),
|
|
set_sgml_parser(Parser, charpos(Start)),
|
|
( caret(_)
|
|
-> retractall(element(_,_,_,_)),
|
|
retractall(stack(_,_,_,_,_)),
|
|
Extra = [ call(begin, emacs_sgml_mode:find_on_begin),
|
|
call(end, emacs_sgml_mode:find_on_end)
|
|
]
|
|
; Extra = []
|
|
),
|
|
catch(sgml_parse(Parser,
|
|
[ source(In),
|
|
parse(element),
|
|
syntax_errors(quiet)
|
|
| Extra
|
|
]),
|
|
E,
|
|
show_message(M, E)),
|
|
% charpos/1 yields start-position
|
|
get_sgml_parser(Parser, charpos(_, To)),
|
|
% format('Found ~d-~d~n', [Start, To]),
|
|
To-1 > Caret.
|
|
find_element(M, Parser, Re, In, Caret, Start0, Range) :-
|
|
get(M, text_buffer, TB),
|
|
send(Re, search, TB, Start0, 0),
|
|
get(Re, register_start, 0, Start1),
|
|
find_element(M, Parser, Re, In, Caret, Start1, Range).
|
|
|
|
find_on_begin(Tag, Attributes, Parser) :-
|
|
get_sgml_parser(Parser, charpos(From, To)),
|
|
get_sgml_parser(Parser, event_class(Class)),
|
|
% format('BEGIN: ~w ~w-~w (~w)~n', [Tag, From, To, Class]),
|
|
asserta(stack(Tag, Attributes, Class, From, To)).
|
|
find_on_end(Tag, Parser) :-
|
|
get_sgml_parser(Parser, charpos(EFrom, ETo)),
|
|
% format('END: ~w ~w-~w~n', [Tag, EFrom, ETo]),
|
|
retract(stack(Tag, Attributes, BClass, BFrom, BTo)),
|
|
caret(Caret),
|
|
( between(BFrom, ETo, Caret)
|
|
-> get_sgml_parser(Parser, event_class(EClass)),
|
|
( element(_,_,_,_)
|
|
-> true
|
|
; assert(element(Tag, Attributes,
|
|
loc(BClass, BFrom, BTo),
|
|
loc(EClass, EFrom, ETo)))
|
|
)
|
|
; true
|
|
).
|
|
|
|
|
|
/*******************************
|
|
* COLOURISING *
|
|
*******************************/
|
|
|
|
colourise_and_recenter(M) :->
|
|
"Colour according to syntax and recenter"::
|
|
send(M, auto_colourise_buffer),
|
|
send(M, recenter).
|
|
|
|
colourise_buffer(M) :->
|
|
OldTime is cputime,
|
|
new(Class, class(sgml_mode_fragment)),
|
|
get(Class, no_created, OldCreated),
|
|
|
|
send(M, setup_styles),
|
|
send(M, remove_syntax_fragments),
|
|
send(M, report, progress, 'Colourising buffer ...'),
|
|
colourise_buffer(M),
|
|
Used is cputime - OldTime,
|
|
get(Class, no_created, NewCreated),
|
|
Created is NewCreated - OldCreated,
|
|
send(M, report, done,
|
|
'done, %.2f seconds, %d fragments', Used, Created).
|
|
|
|
:- dynamic
|
|
current_tb/2.
|
|
|
|
colourise_buffer(M) :-
|
|
make_parser(M, Parser),
|
|
get(M, text_buffer, TB),
|
|
pce_open(TB, read, In),
|
|
colourise(M, Parser,
|
|
[ source(In)
|
|
]),
|
|
free_sgml_parser(Parser).
|
|
|
|
colourise(M, Parser, Options) :-
|
|
get_sgml_parser(Parser, file(File)),
|
|
get(M, text_buffer, TB),
|
|
asserta(current_tb(TB, File), Ref),
|
|
catch(sgml_parse(Parser,
|
|
[ call(begin, emacs_sgml_mode:on_begin),
|
|
call(end, emacs_sgml_mode:on_end),
|
|
call(cdata, emacs_sgml_mode:on_cdata),
|
|
call(decl, emacs_sgml_mode:on_decl),
|
|
call(error, emacs_sgml_mode:on_error)
|
|
| Options
|
|
]),
|
|
E,
|
|
show_message(M, E)),
|
|
erase(Ref).
|
|
|
|
on_begin(_Tag, _Attributes, Parser) :-
|
|
get_sgml_parser(Parser, file(File)),
|
|
current_tb(TB, File),
|
|
% format('Tag ~w, Attr = ~p~n', [Tag, Attributes]),
|
|
get_sgml_parser(Parser, charpos(From, To)),
|
|
get_sgml_parser(Parser, event_class(Class)),
|
|
Class \== omitted,
|
|
colour_item(tag(begin, Class), TB, From, To).
|
|
on_end(_Tag, Parser) :-
|
|
get_sgml_parser(Parser, file(File)),
|
|
current_tb(TB, File),
|
|
get_sgml_parser(Parser, charpos(From, To)),
|
|
get_sgml_parser(Parser, event_class(Class)),
|
|
% format('At ~d-~d: Class = ~w~n', [From, To, Class]),
|
|
Class \== omitted,
|
|
colour_item(tag(end, Class), TB, From, To).
|
|
on_cdata(_CDATA, Parser) :-
|
|
get_sgml_parser(Parser, file(File)),
|
|
current_tb(TB, File),
|
|
get_sgml_parser(Parser, charpos(From, To)),
|
|
( get_sgml_parser(Parser, context([Tag|_]))
|
|
-> ( get_sgml_parser(Parser, dtd(DTD)),
|
|
dtd_property(DTD, element(Tag, _, Model)),
|
|
( Model == cdata
|
|
; Model == rcdata
|
|
)
|
|
-> Type = cdata
|
|
; Type = pcdata
|
|
)
|
|
),
|
|
% format('CDATA from ~d-~d~n', [From, To]),
|
|
colour_item(cdata(Type), TB, From, To, Fragment),
|
|
( Type == cdata
|
|
-> send(Fragment, parsed, @off)
|
|
; true
|
|
).
|
|
on_decl(DECL, Parser) :-
|
|
get_sgml_parser(Parser, file(File)),
|
|
current_tb(TB, File),
|
|
get_sgml_parser(Parser, event_class(explicit)),
|
|
get_sgml_parser(Parser, charpos(From, To)),
|
|
% format('Decl ~d-~d: ~w~n', [From, To, DECL]),
|
|
( DECL == ''
|
|
-> colour_item(comment, TB, From, To, Fragment),
|
|
send(Fragment, parsed, @off)
|
|
; send(regex('DOCTYPE', @off), match, DECL)
|
|
-> colour_item(doctype, TB, From, To)
|
|
; new(Re, regex('\\w*')),
|
|
send(Re, match, DECL),
|
|
get(Re, register_value, DECL, 0, name, DeclType0),
|
|
get(DeclType0, downcase, DeclType),
|
|
% format('Decl(~w)~n', [DeclType]),
|
|
colour_item(decl(DeclType), TB, From, To)
|
|
).
|
|
on_error(Severity, Message, Parser) :-
|
|
current_tb(TB, File),
|
|
( get_sgml_parser(Parser, file(File))
|
|
-> get_sgml_parser(Parser, charpos(From, To)),
|
|
colour_item(error(Severity), TB, From, To, Fragment),
|
|
( Fragment \== @nil
|
|
-> send(Fragment, message, Message),
|
|
send(Fragment, severity, Severity)
|
|
; true
|
|
)
|
|
; format(user_error, 'SGML: Error in other file!~n', [])
|
|
).
|
|
|
|
% colour_item(+Class, +TB, +Pos)
|
|
%
|
|
% colourise region if a style is defined for this class.
|
|
|
|
colour_item(Class, TB, From, To) :-
|
|
colour_item(Class, TB, From, To, _Fragment).
|
|
|
|
colour_item(Class, TB, From, To, Fragment) :-
|
|
style_name(Class, Name), !,
|
|
Len is To - From,
|
|
Len > 0,
|
|
new(Fragment, sgml_mode_fragment(TB, From, Len, Name)).
|
|
colour_item(_, _, _, _, @nil).
|
|
|
|
|
|
/*******************************
|
|
* STYLES *
|
|
*******************************/
|
|
|
|
:- discontiguous
|
|
style_name/2, % +Pattern, -StyleName
|
|
style_object/2. % +Name, -Style
|
|
|
|
term_expansion(style(Pattern, Style),
|
|
[ style_name(Pattern, Name),
|
|
style_object(Name, Style)
|
|
]) :-
|
|
gensym(syntax_style_, Name).
|
|
|
|
style(tag(begin, shortref), style(colour := blue,
|
|
background := grey90,
|
|
bold := @on)).
|
|
style(tag(begin, _), style(colour := blue,
|
|
bold := @on)).
|
|
style(tag(end, shorttag), style(colour := blue,
|
|
bold := @on)).
|
|
style(tag(end, shortref), style(colour := blue,
|
|
background := grey90,
|
|
bold := @on)).
|
|
style(tag(end, _), style(colour := blue)).
|
|
style(cdata(cdata), style(colour := sea_green)).
|
|
style(doctype, style(bold := @on)).
|
|
style(comment, style(colour := dark_green,
|
|
background := grey90)).
|
|
style(decl(_), style(colour := purple)).
|
|
style(error(warning), style(background := orange)).
|
|
style(error(_), style(background := red)).
|
|
style(entity, style(colour := dark_green)).
|
|
style(element, style(background := pale_turquoise)).
|
|
|
|
|
|
/*******************************
|
|
* TAGGING *
|
|
*******************************/
|
|
|
|
set_insert_point(M, Point:[int]) :->
|
|
"Set mark at point if not set"::
|
|
get(M, mark, Mark),
|
|
( Mark == 0
|
|
-> ( Point == @default
|
|
-> send(M, mark, M?caret)
|
|
; send(M, mark, Point)
|
|
)
|
|
; true
|
|
).
|
|
|
|
insert_begin(M, Tag:name) :->
|
|
"Insert begin-tag and required attributes"::
|
|
fix_case(M, Tag, TheTag),
|
|
send(M, format, '<%s>', TheTag),
|
|
get(M, dtd, DTD),
|
|
findall(A, dtd_property(DTD, attribute(Tag, A, _, required)), List),
|
|
send(M, backward_char),
|
|
insert_attributes(List, M),
|
|
send(M, forward_char).
|
|
|
|
|
|
insert_attributes([], _).
|
|
insert_attributes([H|T], M) :-
|
|
send(M, format, ' %s=""', H),
|
|
send(M, set_insert_point, M?caret-1),
|
|
insert_attributes(T, M).
|
|
|
|
|
|
insert_end(M, Tag:name) :->
|
|
"Insert end-tag for element"::
|
|
fix_case(M, Tag, TheTag),
|
|
send(M, format, '</%s>', TheTag).
|
|
|
|
|
|
fix_case(M, Tag, TheTag) :-
|
|
( get(M, upcase_elements, @on)
|
|
-> get(Tag, upcase, TheTag)
|
|
; TheTag = Tag
|
|
).
|
|
|
|
|
|
style_for_event(Ev, Style) :-
|
|
( send(Ev, has_modifier, c)
|
|
-> Style = inline
|
|
; send(Ev, has_modifier, s)
|
|
-> Style = block
|
|
; send(Ev, has_modifier, m)
|
|
-> Style = shorttag
|
|
; Style = @default
|
|
).
|
|
|
|
|
|
show_menu(M, Ev:event) :->
|
|
"Show menu to insert-tag/tag selection"::
|
|
( send(M, in_tag)
|
|
-> send(M, show_attribute_menu, Ev)
|
|
; send(M, show_element_menu, Ev)
|
|
).
|
|
|
|
|
|
in_tag(M) :->
|
|
"Test whether caret is between <>"::
|
|
get(M, caret, Caret),
|
|
get(M, text_buffer, TB),
|
|
send(regex('<[^>]*'), match, TB, Caret, 0),
|
|
send(regex('[^<]*[>/]'), match, TB, Caret). % / for shortag
|
|
|
|
|
|
show_element_menu(M, Ev:event) :->
|
|
"Show menu for inserting a new element"::
|
|
( get(M, allowed_elements, List),
|
|
delete(List, '#pcdata', Elems),
|
|
Elems \== [],
|
|
sort(Elems, Sorted)
|
|
-> ( get(M, selection, point(A,B)), B > A
|
|
-> Label = tag_selection
|
|
; Label = insert_element
|
|
),
|
|
get(Ev, button, Button),
|
|
style_for_event(Ev, Style),
|
|
new(G, popup_gesture(new(P, popup(Label,
|
|
message(M, popup_tag_selection,
|
|
@arg1, Style))),
|
|
Button, new(modifier))),
|
|
send(P, show_label, @on),
|
|
length(Sorted, Len),
|
|
Cols is max(1, Len // 20),
|
|
send(P, columns, Cols),
|
|
send_list(P, append, Sorted),
|
|
send(G, event, Ev)
|
|
; send(M, report, warning, 'No element allowed here')
|
|
).
|
|
|
|
|
|
show_attribute_menu(M, Ev:event) :->
|
|
"Show menu for adding attributes"::
|
|
get(M, caret, Caret),
|
|
get(M, text_buffer, TB),
|
|
new(Re, regex('<[^>]*')),
|
|
send(Re, match, TB, Caret, 0),
|
|
get(Re, register_start, 0, Start),
|
|
( get(M, looking_at_element, Start, E)
|
|
-> make_parser(M, Parser),
|
|
load_dtd(M, Parser),
|
|
get_sgml_parser(Parser, dtd(DTD)),
|
|
dtd_property(DTD, attributes(E, Atts)),
|
|
( Atts == []
|
|
-> free_sgml_parser(Parser),
|
|
send(M, report, warning, 'Element "%s" has no attributes', E)
|
|
; sort(Atts, Sorted),
|
|
% format('Atts = ~p~n', [Sorted]),
|
|
get(Ev, button, Button),
|
|
new(G, popup_gesture(new(P, popup(add_attribute,
|
|
message(M, insert_attribute,
|
|
@arg1))),
|
|
Button,
|
|
new(modifier))),
|
|
send(P, show_label, @on),
|
|
length(Sorted, Len),
|
|
Cols is max(1, Len // 10),
|
|
send(P, columns, Cols),
|
|
fill_attribute_menu(Sorted, DTD, E, P, M),
|
|
free_sgml_parser(Parser),
|
|
send(G, event, Ev)
|
|
)
|
|
; send(M, report, warning, 'Not in begin-tag')
|
|
).
|
|
|
|
|
|
fill_attribute_menu([], _, _, _, _).
|
|
fill_attribute_menu([A|T], DTD, E, P, Mode) :-
|
|
dtd_property(DTD, attribute(E, A, Type, Default)),
|
|
add_attribute_menu(Type, Default, A, P, Mode),
|
|
fill_attribute_menu(T, DTD, E, P, Mode).
|
|
|
|
add_attribute_menu(nameof(List), Def, A, P, Mode) :- !,
|
|
send(P, append, new(P2, popup(A, message(Mode, insert_attribute,
|
|
A, @arg1)))),
|
|
add_attribute_values(List, Def, P2).
|
|
add_attribute_menu(Type, Def, A, P, _Mode) :- !,
|
|
type_label(Type, TypeLabel),
|
|
send(P, append, new(MI, menu_item(A, @default,
|
|
string('%s (%s)', A, TypeLabel)))),
|
|
( Def == required
|
|
-> send(MI, font, bold)
|
|
; true
|
|
).
|
|
|
|
type_label(list(Type), Label) :- !,
|
|
atom_concat(Type, s, Label).
|
|
type_label(Type, Type).
|
|
|
|
add_attribute_values([], _, _).
|
|
add_attribute_values([H|T], Def, P) :-
|
|
send(P, append, new(MI, menu_item(H))),
|
|
( Def == default(H)
|
|
-> send(MI, font, bold)
|
|
; true
|
|
),
|
|
add_attribute_values(T, Def, P).
|
|
|
|
|
|
insert_attribute(M, Att:name, Val:'[name|int|real]') :->
|
|
"Add attribute-value pair"::
|
|
get(M, text_buffer, TB),
|
|
get(M, caret, Caret),
|
|
new(Re, regex('\\(\\s +\\|[/>]\\)')),
|
|
send(Re, search, TB, Caret), % find place to insert
|
|
get(Re, register_start, 0, Where),
|
|
( send(regex('\\s +'), match, TB, Where)
|
|
-> get(Re, register_end, 0, NewCaret), % after blanks
|
|
send(M, caret, NewCaret)
|
|
; send(M, caret, Where),
|
|
send(M, format, ' ')
|
|
),
|
|
( Val == @default
|
|
-> send(M, format, '%s=""', Att),
|
|
get(M, caret, C),
|
|
( send(M, looking_at, '\\s \\|[/>]')
|
|
-> true
|
|
; send(M, format, ' ')
|
|
),
|
|
send(M, caret, C-1)
|
|
; send(M, format, '%s="%s"', Att, Val),
|
|
( send(M, looking_at, '\\s \\|[/>]')
|
|
-> true
|
|
; send(M, format, ' ')
|
|
)
|
|
),
|
|
send(M, mark_undo). % called from popup!
|
|
|
|
|
|
popup_tag_selection(M, Tag:name, Style0:[{inline,shorttag,block}]) :->
|
|
"->tag_selection wrapper for popup"::
|
|
( Style0 == @default
|
|
-> style_for_event(@event, Style)
|
|
; Style = Style0
|
|
),
|
|
send(M, tag_selection, Tag, Style),
|
|
send(M, mark_undo).
|
|
|
|
|
|
tag_selection(M, Tag:[name], Style:[{inline,block,shorttag}]) :->
|
|
"Tag the current selection using element"::
|
|
( Tag == @default
|
|
-> new(TI, text_item(element)),
|
|
( get(M, allowed_elements, List),
|
|
% format('Allowed: ~p~n', [List]),
|
|
delete(List, '#pcdata', Elems),
|
|
sort(Elems, Sorted)
|
|
-> send(TI, value_set, Sorted)
|
|
; true
|
|
),
|
|
get(M, prompt_using, TI, String),
|
|
get(String, value, TheTag)
|
|
; TheTag = Tag
|
|
),
|
|
( get(M, selection, point(A,B)),
|
|
B > A
|
|
-> send(M, tag_region, TheTag, A, B, Style),
|
|
send(M, selection, 0, 0),
|
|
send(M, colourise_element)
|
|
; send(M, insert_element, TheTag, Style)
|
|
).
|
|
|
|
|
|
tag_region(M, Tag:[name], From:int, To:int,
|
|
Style:[{inline,block,shorttag}]) :->
|
|
"Tag a defined region"::
|
|
fix_case(M, Tag, TheTag),
|
|
get(M, text_buffer, TB),
|
|
( Style == shorttag
|
|
-> send(TB, insert, To, /),
|
|
send(TB, insert, From, string('<%s/', TheTag))
|
|
; Style == block
|
|
-> ( get(M, column, To, 0)
|
|
-> send(TB, insert, To, string('</%s>\n', TheTag))
|
|
; send(TB, insert, From, string('\n</%s>\n', TheTag))
|
|
),
|
|
( get(M, column, From, 0)
|
|
-> send(TB, insert, From, string('<%s>\n', TheTag))
|
|
; send(TB, insert, From, string('\n<%s>\n', TheTag))
|
|
)
|
|
; Style == inline
|
|
-> send(TB, insert, To, string('</%s>', TheTag)),
|
|
send(TB, insert, From, string('<%s>', TheTag))
|
|
; get(M, column, From, 0),
|
|
get(M, column, To, 0)
|
|
-> send(M, tag_region, Tag, From, To, block)
|
|
; send(M, tag_region, Tag, From, To, inline)
|
|
).
|
|
|
|
|
|
insert_element(M, Tag:element=name, Style:[{inline,shorttag,block}]) :->
|
|
"Insert a new empty element"::
|
|
( get(M, dtd, DTD),
|
|
dtd_property(DTD, element(Tag, Omit, Content))
|
|
-> true
|
|
; Omit = omit(false, false),
|
|
Content = '#pcdata'
|
|
),
|
|
fix_case(M, Tag, TheTag),
|
|
send(M, prepare_insert),
|
|
send(M, mark, 0), % put insert position here
|
|
insert_by_style(Style, M, TheTag, Omit, Content, _),
|
|
send(M, colourise_element),
|
|
( get(M, mark, Mark),
|
|
Mark > 0
|
|
-> send(M, caret, Mark)
|
|
; true
|
|
).
|
|
|
|
prepare_insert(M) :->
|
|
"Find location to insert a new tag"::
|
|
get(M, caret, Caret),
|
|
( find_element(M, Caret, From-_To)
|
|
-> get(M, looking_at_element, From, E),
|
|
% format('~p: Inserting in "~w" at ~w~n', [M, E, From]),
|
|
get(M, dtd, DTD),
|
|
dtd_property(DTD, element(E, _, Content)),
|
|
( mixed_content(Content)
|
|
-> true
|
|
; get(M, column, From, Col0),
|
|
Col is Col0+2,
|
|
get(M, text_buffer, TB),
|
|
get(TB, scan, Caret, line, 0, start, SOL),
|
|
( new(Re, regex('\\s *')),
|
|
send(Re, match, TB, SOL, Caret),
|
|
get(Re, register_end, Caret)
|
|
-> true % at a blank line
|
|
; send(M, newline)
|
|
),
|
|
send(M, align_line, Col)
|
|
)
|
|
; true
|
|
).
|
|
|
|
insert_by_style(_, M, Tag, _, empty, End) :- !,
|
|
send(M, insert_begin, Tag),
|
|
( get(M, dialect, xml)
|
|
-> send(M, backward_char),
|
|
send(M, format, /),
|
|
send(M, forward_char)
|
|
; true
|
|
),
|
|
get(M, caret, End).
|
|
insert_by_style(Style, M, Tag, _, Model, End) :-
|
|
required_content(Model, List),
|
|
( mixed_content(Model)
|
|
-> def_style(Style, inline, TheStyle),
|
|
insert_by_style(TheStyle, M, Tag, End),
|
|
send(M, set_insert_point)
|
|
; insert_by_style(block, M, Tag, End0),
|
|
get(M, text_buffer, TB),
|
|
new(Mark, fragment(TB, End0, 0)),
|
|
insert_sub_elements(List, M),
|
|
get(Mark, start, End),
|
|
free(Mark)
|
|
).
|
|
|
|
mixed_content(M) :-
|
|
term_member('#pcdata', M), !.
|
|
|
|
term_member(X, X).
|
|
term_member(X, C) :-
|
|
compound(C),
|
|
arg(_, C, A),
|
|
term_member(X, A).
|
|
|
|
insert_by_style(shorttag, M, Tag, End) :- !,
|
|
send(M, insert_begin, Tag),
|
|
send(M, backward_delete_char),
|
|
send(M, format, '//'),
|
|
get(M, caret, End),
|
|
send(M, backward_char).
|
|
insert_by_style(inline, M, Tag, End) :- !,
|
|
send(M, insert_begin, Tag),
|
|
get(M, caret, New),
|
|
send(M, insert_end, Tag),
|
|
get(M, caret, End),
|
|
send(M, caret, New).
|
|
insert_by_style(block, M, Tag, End) :- !,
|
|
send(M, insert_begin, Tag),
|
|
get(M, caret, Insert),
|
|
send(M, newline_and_indent),
|
|
send(M, insert_end, Tag),
|
|
get(M, caret, End),
|
|
send(M, caret, Insert).
|
|
insert_by_style(@default, M, Tag, End) :-
|
|
get(M, text_buffer, TB),
|
|
get(M, caret, Caret),
|
|
get(TB, scan, Caret, line, 0, start, SOL),
|
|
( send(regex('\\s *$'), match, TB, SOL)
|
|
-> insert_by_style(block, M, Tag, End)
|
|
; insert_by_style(inline, M, Tag, End)
|
|
).
|
|
|
|
|
|
def_style(@default, Style, Style) :- !.
|
|
def_style(Style, _, Style).
|
|
|
|
insert_sub_elements([], _).
|
|
insert_sub_elements([H|T], M) :-
|
|
send(M, format, ' '),
|
|
get(M, dtd, DTD),
|
|
dtd_property(DTD, element(H, Omit, Content)),
|
|
send(M, prepare_insert),
|
|
insert_by_style(@default, M, H, Omit, Content, End),
|
|
( T == []
|
|
-> true
|
|
; send(M, caret, End),
|
|
insert_sub_elements(T, M)
|
|
).
|
|
|
|
|
|
required_content(empty, []).
|
|
required_content(cdata, []).
|
|
required_content(Model, Elems) :-
|
|
phrase(required_content(Model), Elems).
|
|
|
|
required_content((A,B)) --> !,
|
|
required_content(A),
|
|
required_content(B).
|
|
required_content(&(A,B)) --> !,
|
|
required_content(A),
|
|
required_content(B).
|
|
required_content('|'(_,_)) --> !,
|
|
[].
|
|
required_content(?(_)) -->
|
|
[].
|
|
required_content(*(_)) -->
|
|
[].
|
|
required_content(+(A)) -->
|
|
required_content(A).
|
|
required_content('#pcdata') --> !,
|
|
[].
|
|
required_content(A) -->
|
|
[A].
|
|
|
|
|
|
looking_at_element(M, From:int, Elem:name) :<-
|
|
new(Re, regex('<\\([-_:a-zA-Z0-9]+\\)')),
|
|
get(M, text_buffer, TB),
|
|
send(Re, match, TB, From),
|
|
get(Re, register_value, TB, 1, name, Elem).
|
|
|
|
|
|
allowed_elements(M, Allowed:prolog) :<-
|
|
"Show elements allowed here"::
|
|
get(M, caret, Caret),
|
|
get(M, text_buffer, TB),
|
|
new(Re, regex('<\\w+')),
|
|
make_parser(M, Parser),
|
|
load_dtd(M, Parser),
|
|
get_sgml_parser(Parser, dtd(DTD)),
|
|
set_sgml_parser(Parser, doctype(_)),
|
|
pce_open(TB, read, In),
|
|
set_caret(Caret),
|
|
( find_element(M, Parser, Re, In, Caret, From-_To),
|
|
get(M, looking_at_element, From, E),
|
|
% format('Looking at ~w~n', [E]),
|
|
( dtd_property(DTD, doctype(E))
|
|
; dtd_property(DTD, element(E, omit(_, false), _))
|
|
)
|
|
-> unset_caret,
|
|
seek(In, From, bof, _),
|
|
set_sgml_parser(Parser, charpos(From)),
|
|
Len is Caret - From,
|
|
catch(sgml_parse(Parser,
|
|
[ goal(emacs_sgml_mode:feed(In, Len)),
|
|
syntax_errors(quiet),
|
|
parse(input) % do not complete document
|
|
]),
|
|
E,
|
|
show_message(M, E)),
|
|
( element(_,_,_,loc(explicit,_,_))
|
|
-> format('End-tag available~n', [])
|
|
; true
|
|
),
|
|
get_sgml_parser(Parser, allowed(Allowed))
|
|
; unset_caret,
|
|
dtd_property(DTD, doctype(DocType)),
|
|
atom(DocType)
|
|
-> Allowed = [DocType]
|
|
; send(M, report, warning, 'No current element'),
|
|
Allowed = []
|
|
),
|
|
close(In),
|
|
free_sgml_parser(Parser).
|
|
|
|
feed(In, Len, Parser) :-
|
|
copy_stream_data(In, Parser, Len).
|
|
|
|
report_allowed(M) :-> % DEBUGGING
|
|
"Report allowed elements at point"::
|
|
get(M, allowed_elements, Allowed),
|
|
atomic_list_concat(Allowed, ', ', Atom),
|
|
send(M, report, status, 'Allowed: %s', Atom).
|
|
|
|
show_message(M, E) :-
|
|
message_to_string(E, String),
|
|
send(M, report, warning, 'Caught error: %s', String).
|
|
|
|
|
|
/*******************************
|
|
* MOVING AROUND *
|
|
*******************************/
|
|
|
|
forward_move_out(M) :->
|
|
"Move forwards to end of current element"::
|
|
get(M, caret, Caret),
|
|
( find_element(M, Caret, _From-To)
|
|
-> send(M, caret, To)
|
|
; send(M, report, warning, 'Cannot find element')
|
|
).
|
|
|
|
:- emacs_end_mode.
|
|
|
|
|
|
|
|
/*******************************
|
|
* XML *
|
|
*******************************/
|
|
|
|
:- emacs_begin_mode(xml, sgml,
|
|
"Mode for editing XML documents",
|
|
[],
|
|
[]).
|
|
|
|
initialise(M) :->
|
|
send_super(M, initialise),
|
|
send(M, dialect, xml).
|
|
|
|
open_document(M, DTD:doctype=name) :->
|
|
"Insert document header"::
|
|
send(M, format, '<?xml version="1.0"?>\n'),
|
|
send_super(M, open_document, DTD).
|
|
|
|
:- emacs_end_mode.
|
|
|
|
|
|
/*******************************
|
|
* HTML *
|
|
*******************************/
|
|
|
|
:- emacs_begin_mode(html, sgml,
|
|
"Mode for editing HTML documents",
|
|
[],
|
|
[]).
|
|
|
|
initialise(M) :->
|
|
send_super(M, initialise),
|
|
send(M, dialect, html).
|
|
|
|
open_document(M) :->
|
|
"Insert document header"::
|
|
send(M, format,
|
|
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">\n\n'),
|
|
send(M, insert_element, html).
|
|
|
|
:- emacs_end_mode.
|
|
|
|
|
|
/*******************************
|
|
* FRAGMENT *
|
|
*******************************/
|
|
|
|
:- pce_begin_class(sgml_mode_fragment, emacs_colour_fragment,
|
|
"Provide colourised region").
|
|
|
|
variable(parsed, bool := @on, both, "@off for unparsed fragments").
|
|
|
|
:- pce_end_class.
|