1082 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
		
		
			
		
	
	
			1082 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
|   | /*  $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), | ||
|  | 	concat_atom(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. |