% File: latex_external.sl -*- mode: SLang; mode: fold -*- % % Copyright (c) % until 2003 Guido Gonzato (as Latex4Jed) % 2003--2007 Jörg Sommer % $Id: latex_external.sl 199 2007-08-23 23:28:52Z joerg $ % % -*- This file is part of Jörg's LaTeX Mode (JLM) -*- % % Description: In this file are all functions for composing, viewing, % printing and any related problems. All what have to do % with an external program and a latex file. % % License: 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. % Fixme ToDo: % + replace all error()s with throw if (length(where("latex_external" == _get_namespaces()))) use_namespace("latex_external"); else implements("latex_external"); #ifnexists profile_on % profiling does not work with stack check enabled. autoload("Global->enable_stack_check", "stkcheck"); try { enable_stack_check(); } catch OpenError: {} % stkcheck not found in the path #endif #ifnexists any public define any(array) { return wherefirst(array) != NULL; } #endif #ifnexists all public define all(array) { return not any(not array); } #endif %%%%%%%%%% % % Declarations needed somewhere before the definition % static define view() { throw NotImplementedError; } %%%%%%%%%% % % Tools % #if (_jed_version < 9919) public define buffer_filename () { variable args = __pop_args (_NARGS); variable file, dir; (file, dir, , ) = getbuf_info(__push_args (args)); !if (strlen (file)) dir = ""; return dir + file; } #endif private define path_concat(left, right) { if (path_basename(left) == "") % left has a trailing dir separator left = path_dirname(left); forever { #ifdef UNIX if (strncmp(right, "../", 3) == 0) #else if (strncmp(right, "..\\", 3) == 0) #endif { left = path_dirname(left); right = substr(right, 4, -1); } #ifdef UNIX else if (strncmp(right, "./", 2) == 0) #else else if (strncmp(right, ".\\", 2) == 0) #endif right = substr(right, 3, -1); else break; } return Global->path_concat(left, right); } private define find_buf_of_file(fname) { if (fname == NULL) return NULL; foreach ( __pop_args( buffer_list() ) ) { variable bname = ().value; !if ( strcmp(buffer_filename(bname), fname) ) return bname; } return NULL; } private variable MODE="LaTeX-External"; %%%%%%%%%% % % Customisation stuff % %!%+ %\variable{String_Type LaTeX_Rerun} %\synopsis{rerun composing} %\description % This variable defines the configuration of the commands used for % creating and viewing the document and so on. % % Default is "latex". %\seealso{cust_set()} %!%- custom_variable("LaTeX_Config", "latex"); % %m Masterfile % %M Masterfile without extension % \see{expand_and_run_cmd} typedef struct { name, desc, bibtex, index, latex, post, clean, mrproper, print, view, rerun } cust_type; private variable cust = Assoc_Type[cust_type]; static define cust_set(name, desc, bibtex, index, latex, post, clean, mrproper, print, view, rerun) { variable accept_NULL = assoc_key_exists(cust, name); !if ( typeof(rerun) == Integer_Type or (accept_NULL and rerun == NULL) ) throw InvalidParmError, "rerun must be an integer"; variable types = [typeof(desc), typeof(bibtex), typeof(index), typeof(latex), typeof(post), typeof(clean), typeof(mrproper), typeof(print), typeof(view)]; !if ( all( (types == String_Type) or (accept_NULL and (types == Null_Type)) ) ) throw InvalidParmError, "At least one of the given arguments has a "+ "wrong data type"; variable tmp; if (accept_NULL) { tmp = cust[name]; if (desc != NULL) tmp.desc = desc; if (bibtex != NULL) tmp.bibtex = bibtex; if (index != NULL) tmp.index = index; if (latex != NULL) tmp.latex = latex; if (post != NULL) tmp.post = post; if (clean != NULL) tmp.clean = clean; if (mrproper != NULL) tmp.mrproper = mrproper; if (print != NULL) tmp.print = print; if (view != NULL) tmp.view = view; if (rerun != NULL) tmp.rerun = rerun; } else { tmp = @cust_type; cust[name] = tmp; tmp.name = name; tmp.desc = desc; tmp.bibtex = bibtex; tmp.index = index; tmp.latex = latex; tmp.post = post; tmp.clean = clean; tmp.mrproper = mrproper; tmp.print = print; tmp.view = view; tmp.rerun = rerun; } } cust_set("rubber_pdf", "Run rubber to build a PDF", "", "", "rubber --inplace --pdf %m", "", "rubber --clean %m", "rubber --clean --pdf %m", "lp -t \"%M\" %M.pdf", "xpdf %M.pdf", 0); cust_set("rubber_dvi", "Run rubber to build a DVI", "", "", "rubber --inplace %m", "", "", "rubber --clean %m", "sh -c dvips% -o-% %M.dvi|lp% -t% \"%M\"", "xdvi %M.dvi", 0); cust_set("rubber_ps", "Run rubber to build a PS", "", "", "rubber --inplace --ps %m", "", "rubber --clean %m", "rubber --clean --ps %m", "lp -t %M %M.ps", "gv --watch %M.ps", 0); cust_set("rubber_ps_pdf", "Run rubber to build a PDF from PS", "", "", "rubber --inplace --ps --pdf %m", "", "rubber --clean --ps %m", "rubber --clean --ps --pdf %m", "lp -t %M %M.pdf", "xpdf %M.pdf", 0); cust_set("latex", "Run LaTeX", "bibtex %m", "makeindex %M.idx", "latex -interaction=nonstopmode %m", "", #ifdef MSWINDOWS "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc", "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi", "", "yap %M.dvi", 1); #else "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc", "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi", "sh -c dvips% -o-% \"%M.dvi\"|lp% -t% \"%M\"", "xdvi %M.dvi", 1); #endif cust_set("pdflatex", "Run PDFLaTeX with Xindy ", "bibtex %m", "texindy --quiet --language german-din %M.idx", "pdflatex -interaction=nonstopmode %m", "", #ifdef MSWINDOWS "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc", "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.pdf", "", #else "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc", "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.pdf", "lp -t %M %M.pdf", #endif "xpdf %M.pdf", 1); cust_set("latex_ps", "Run LaTeX and dvips", "bibtex %m", "makeindex %M.idx", "latex -interaction=nonstopmode %m", "dvips %M.dvi", #ifdef MSWINDOWS "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi", "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi %M.ps", "", #else "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi", "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi %M.ps", "lp -t %M %M.ps", #endif "gv %M.ps", 1); cust_set("latex_srcspecl", "LaTeX with source specials ", "bibtex %m", "makeindex %M.idx", #ifdef MSWINDOWS "latex -interaction=nonstopmode --src-specials %m", "", "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc", "del %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi", "", "yap --find-src-special %l%f %M.dvi", 1); #else "latex -interaction=nonstopmode -src-specials %m", "", "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc", "rm -f %M.aux %M.idx %M.ind %M.log %M.out %M.toc %M.dvi", "", "xdvi -nofork -editor %e -sourceposition %l:%c%f %M.dvi", 1); #endif static define cust_jump_or_edit() { variable mark = create_user_mark(); bol(); if ( eolp() or what_line < 3 ) % empty line or a line before the TOC -- nothing should happen goto_user_mark(mark); else if ( looking_at_char('*') ) % a line in the TOC -- jump to the section { () = right(2); push_mark(); !if ( ffind(" -- ") ) throw InternalError, "buf structure destroyed"; bskip_white(); !if ( re_fsearch( "^"+bufsubstr()+"$" ) ) throw InternalError, "buf structure destroyed"; } else if ( ffind_char(':') ) % a config line -- edit it { () = right(1); push_spot(); bol(); push_mark(); pop_spot(); str_delete_chars(bufsubstr(), " "); % prompt for read_mini() on stack ""; % default for read_mini() on stack () = right(1); push_spot(); push_mark(); eol(); bufsubstr(); % init for read_mini() on stack pop_spot(); try { read_mini((), (), ()); set_readonly(0); push_mark(); eol(); del_region(); insert( () ); % from read_mini() set_readonly(1); set_buffer_modified_flag(0); % Fixme: fill in code throw NotImplementedError; } catch UserBreakError: {} bol(); } else % this must be a line with the config name % Fixme: fill in code throw NotImplementedError; } static define cust_select() { variable mark = create_user_mark(), name; bol(); if ( eolp() or what_line < 3 ) { % empty line or a line before the TOC -- nothing should happen goto_user_mark(mark); return; } else if ( looking_at_char('*') ) % a line in the TOC { () = right(2); push_mark(); !if ( ffind(" -- ") ) throw InternalError, "buf structure destroyed"; bskip_white(); name = bufsubstr(); } else { % search the last empty line backwards while( not (bol(), eolp()) ) () = up(1); () = down(1); name = line_as_string(); } if (name == "new_conf") throw UsageError, "This is a pseudo confic. Give it a name and you can load it."; else LaTeX_Config = name; delbuf( whatbuf() ); } !if ( keymap_p(MODE + "-cust") ) { variable _mode = MODE + "-cust"; make_keymap(_mode); definekey("latex_external->cust_jump_or_edit()", "\n", _mode); definekey("latex_external->cust_jump_or_edit()", "\r", _mode); definekey("latex_external->cust_select()", " ", _mode); definekey("delbuf(whatbuf());call(\"delete_window\")", "q", _mode); definekey("delbuf(whatbuf());call(\"delete_window\")", "c", _mode); } private define cust_view_insert(name, desc, bibtex, index, latex, post, clean, mrproper, print, view, rerun) { insert("* "+name); whitespace(15-strlen(name)); % The three spaces after -- are a hack for DFA highlighting insert(" -- "+desc+"\n"); push_spot(); % Fixme: show the keys for the command before the colon; % use which_key to find the key eob(); newline(); insert(name); newline(); insert("Description : "+desc); newline(); insert("Bibtex : "+bibtex); newline(); insert("Index : "+index); newline(); insert("LaTex : "+latex); newline(); insert("Post proc. : "+post); newline(); insert("Cleanup : "+clean); newline(); insert("Mr. Proper : "+mrproper); newline(); insert("Print : "+print); newline(); insert("View : "+view); newline(); insert("Rerun LaTeX : "+rerun); newline(); pop_spot(); } #ifdef HAS_DFA_SYNTAX private define setup_dfa_callback(name) { % the header dfa_define_highlight_rule("^Possible configurations", "comment", name); dfa_define_highlight_rule("^-*$", "comment", name); dfa_define_highlight_rule("^Current configuration:.*", "comment", name); % in the toc dfa_define_highlight_rule("^\\* ", "preprocess", name); dfa_define_highlight_rule("-- ", "preprocess", name); % this matches the part between * and -- dfa_define_highlight_rule("^[^\\*:]*$", "keyword", name); % this matches the part after -- dfa_define_highlight_rule(" .*$", "string", name); % this matches the configuration name before the section dfa_define_highlight_rule("[a-zA-Z_ ]* ", "keyword", name); % this mathes the part before the colon in the section dfa_define_highlight_rule("^[a-zA-Z\\. ]*:", "number", name); % ... and this the part after the colon dfa_define_highlight_rule(" .*$", "normal", name); dfa_build_highlight_table(name); } create_syntax_table(MODE + "-cust"); dfa_set_init_callback(&setup_dfa_callback(), MODE + "-cust"); enable_dfa_syntax_for_mode(MODE + "-cust"); #endif static define cust_view() { sw2buf("LaTeX-cust"); set_mode(MODE + "-cust", 0); use_keymap(MODE + "-cust"); #ifdef HAS_DFA_SYNTAX use_syntax_table(MODE + "-cust"); #endif set_status_line("Select LaTeX build tools -- Space to select and "+ "Enter to edit", 0); erase_buffer(); insert("Possible configurations\n-----------------------\n"); insert("Current configuration: $LaTeX_Config\n\n"$); variable tmp = assoc_get_keys(cust); foreach ( tmp[array_sort(tmp)] ) { variable conf = (); conf = cust[conf]; cust_view_insert(conf.name, conf.desc, conf.bibtex, conf.index, conf.latex, conf.post, conf.clean, conf.mrproper, conf.print, conf.view, string(conf.rerun)); } cust_view_insert("new_conf", "setup a new configuration", "command to run Bibtex", "command to build the index", "command to generate the output file", "command to run after a successful latex run", "command to remove all temporary files", "command to all files execpt the source file", "command to print the output file", "command to view the output file", "rerun LaTeX?"); set_readonly(1); set_buffer_modified_flag(0); bob(); } %%%%%%%%%% % % All about the master file stuff % %!%+ %\function{select_master_file} %\synopsis{sets the usage of a master file for compilation} %\usage{select_master_file()} %\description % Asks the user if the file in the current buffer should be used as master % file for composing. This is helpful is you use \include in latex. If set % everywhere you call compose() latex is called with the file set here. % %\seealso{mater_file, master_dir, get_compose_dirfile()} %!%- static define select_master_file() { try { #ifexists jcomplete_file variable file = jmini_prompt_with_completion("Select master file:", "", "", &jcomplete_file); #else variable file = read_with_completion("Select master file:", "", "", 'f'); #endif if (file == "") file = NULL; else !if ( path_is_absolute(file) ) file = path_concat(getcwd(), file); !if ( blocal_var_exists("LaTeX_master_file") ) create_blocal_var("LaTeX_master_file"); set_blocal_var(file, "LaTeX_master_file"); } catch UserBreakError; } %!%+ %\function{get_master_file} %\synopsis{Get file name of the latex file} %\usage{String_Type get_master_file()} %\description % This function returns the full path of the file, which is used by % the latex command. % % This could be used to determine the name of the .dvi or .ps file. %!%- private define get_master_file() { if ( blocal_var_exists("LaTeX_master_file") ) { variable file = get_blocal_var("LaTeX_master_file"); if ( file != "" and file != NULL ) { if ( path_is_absolute(file) ) return file; else return path_concat(path_dirname(buffer_filename()), file); } } return buffer_filename(); } static define jump_to_master_buffer() { !if ( blocal_var_exists("LaTeX_master_file") ) return; variable master_buf = get_blocal_var("LaTeX_master_file"); if (master_buf == NULL or master_buf == "") return; !if ( path_is_absolute(master_buf) ) master_buf = path_concat(path_dirname(buffer_filename()), master_buf); variable buf = find_buf_of_file(master_buf); if (buf == NULL) throw InternalError, "Buffer with master file not found"; setbuf(buf); } private define exists_master_var(name) { variable oldbuf = whatbuf(); jump_to_master_buffer(); blocal_var_exists("LaTeX_"+name); setbuf(oldbuf); return (); } private define get_master_var() % optional 2nd arg: default { variable dflt, name; if (_NARGS == 2) dflt = (); name = (); variable oldbuf = whatbuf(); jump_to_master_buffer(); try { if (_NARGS != 2 or blocal_var_exists("LaTeX_"+name)) return get_blocal_var("LaTeX_"+name); return dflt; } finally { setbuf(oldbuf); } } private define set_master_var(val, name) { variable oldbuf = whatbuf(); jump_to_master_buffer(); create_blocal_var("LaTeX_"+name); set_blocal_var(val, "LaTeX_"+name); setbuf(oldbuf); } %%%%%%%%%% % % external command stuff % private variable DUMMY_OUTPUT_BUFFER = " latex-process-output"; private variable outp_buf=""; private define output_to_msg(pid, data) { variable outp = strchop(outp_buf + data, '\n', 0); % preserve all after the last \n for the next message outp_buf = outp[-1]; if (length(outp) > 1) { message("(latex) " + outp[-2]); update(0); } } %!%+ %\variable{User_Mark line_mark} %\synopsis{holds the mark of the current line} %\description % we need this variable as buffer for the line mark. % if the line mark isn't associated with a variable it isn't shown % %\seealso{update_log_hook()} %!%- private variable line_mark; %!%+ %\function{update_log_hook} %\synopsis{Marks the current line} %\usage{update_log_hook()} %\description % This function marks the current line for highlighting. % % It is used in the buffers with the output of latex and other programms % to show better the line the cursor is in. %!%- private define update_log_hook() { line_mark = create_line_mark(color_number("region")); } private define expand_and_run_cmd(cmd, buf) { variable len = strlen(cmd); if (cmd == NULL or len == 0) return; variable master_file = get_master_file(); variable rel_master_file = path_basename(master_file); variable cmd_list = list_new(), next_arg = "", last_was_percent = 0; foreach (cmd) { variable ch = (); if (last_was_percent) { switch (ch) { case '%': next_arg += "%"; } { case 'c': next_arg += string(what_column()); } { case 'f': if (buffer_filename() == "") throw InternalError, "Buffer without filename: " + whatbuf(); % Fixme: this path should be relativ next_arg += buffer_filename(); } { case 'l': next_arg += string(what_line()); } { case 'm': next_arg += rel_master_file; } { case 'M': next_arg += path_sans_extname(rel_master_file); } { case ' ': next_arg += " "; } { throw InvalidParmError, "Invalid escape sequence in cmd: %"+ch; } last_was_percent = 0; } else { switch (ch) { case ' ': list_append(cmd_list, next_arg, -1); next_arg = ""; } { case '%': last_was_percent = 1; } { next_arg += char(ch); } } } if (last_was_percent) next_arg += "%"; if (strlen(next_arg) != 0) list_append(cmd_list, next_arg, -1); if ( chdir( path_dirname(master_file) ) ) throw IOError, "Could not change directory"; variable oldbuf = whatbuf(); if (typeof(buf) == String_Type) { variable buffer_exists = bufferp(buf); setbuf(buf); % set it in all cases, maybe we don't compose the same file create_blocal_var("LaTeX_master_file"); set_blocal_var(master_file, "LaTeX_master_file"); if (buffer_exists) { % buffer already exists set_readonly(0); erase_buffer(); } else { % buffer is new set_buffer_hook("update_hook", &update_log_hook()); } } if ( cmd[0] == '@' ) { try eval(cmd[[1:]]); finally setbuf(oldbuf); } else { variable pid; try { foreach (cmd_list) ; % push elements on stack pid = open_process_pipe( length(cmd_list)-1 ); } finally setbuf(oldbuf); process_query_at_exit(pid, 1); if (typeof(buf) == Ref_Type) set_process(pid, "output", buf); return pid; } } %%%%%%%%%% % % General log files (bibtex, makeindex, tex) % private variable SIMPLE_KEYMAP = MODE + "-simple"; !if ( keymap_p(SIMPLE_KEYMAP) ) { make_keymap(SIMPLE_KEYMAP); definekey("delbuf(whatbuf());call(\"delete_window\")", "q", SIMPLE_KEYMAP); definekey("delete_window", "c", SIMPLE_KEYMAP); } private define show_log_file(log_file) { if (stat_file(log_file) == NULL) throw ApplicationError, "Log file does not exist"; % moves the current line in the upper half of the window, else the new % window is opened on the upper border if (window_line() >= window_info('r')/2) { recenter( window_info('r')/2-3 ); update_sans_update_hook(1); } variable buf = find_buf_of_file(log_file); variable open_new_buffer = 1; if (buf != NULL) { setbuf(buf); if ( _test_buffer_flag(0x004) ) delbuf(whatbuf()); else open_new_buffer = 0; } if (open_new_buffer) { if (get_master_var("compose_pid", -1) != -1) throw UsageError, "A compose process is running. I cannot open the log file"; () = read_file(log_file); set_readonly(1); set_buffer_modified_flag(0); use_keymap(SIMPLE_KEYMAP); set_status_line(" " + path_basename(log_file) + " ", 0); } pop2buf( whatbuf() ); window_info('r')-6; % leaves its return code on stack otherwindow(); loop( () ) enlargewin(); % shrinks the other window to a size of 6 rows otherwindow(); return open_new_buffer; } %%%%%%%%%% % % All about parsing the TeX log file % %!%+ %\function{find_next_error} %\synopsis{Moves the cursor to the line with the next error} %\usage{find_next_error} %\description % This function moves the editing point to the next line, taht contains % an error or a warning message of latex. % % It stops on font warning, undefined references, over and underfull boxes, % and all error messages. %!%- % dir == 1 -> forward, dir != 0 -> backward static define find_next(what, dir) { variable move; if (dir) move = &down(); else move = &up(); variable found = ' '; variable old_mark = create_user_mark(), old_err_text; if ( looking_at("! ") ) { push_mark(); () = bol_fsearch("l."); () = down(1); old_err_text = bufsubstr(); } do { while (@move(1)) { bol(); switch ( what_char() ) { case 'L' or case 'P': if ( orelse {looking_at("LaTeX")} {looking_at("Package")} ) { if ( ffind("Warning:") ) { found = 'w'; bol(); break; } % else % { % if ( ffind("Info:") ) % found = 'i'; % else % continue; % } } } { case 'O': if (looking_at("Overfull")) { found = 'b'; break; } } { case 'U': if (looking_at("Underfull")) { found = 'b'; break; } } { case '!': if ( andelse {looking_at("! ")} {not ( andelse {__is_initialized(&old_err_text)} {looking_at(old_err_text)} ) } ) { found = 'e'; break; } } { case 'R': if (looking_at("Runaway argument?")) { found = 'e'; break; } } } } while (andelse {what != '*'} {what != found} {not bobp()} {eol(), not eobp()}); if (bobp() or eobp()) { goto_user_mark(old_mark); switch (what) { case 'e': throw UsageError, "No error message left"; } { case 'w': throw UsageError, "No warning message left"; } { case 'b': throw UsageError, "No box message left"; } { case '*': throw UsageError, "No message left"; } { throw InvalidParmError, "unknown search target: "+char(what); } } } !if ( keymap_p(MODE) ) { copy_keymap(MODE, SIMPLE_KEYMAP); variable key; foreach key (['e', 'w', 'b']) { definekey("latex_external->find_next('"+char(key)+"', 0)", char(key-0x20), MODE); definekey("latex_external->find_next('"+char(key)+"', 1)", char(key), MODE); } definekey("latex_external->find_next('*', 0)", "N", MODE); definekey("latex_external->find_next('*', 1)", "n", MODE); definekey("latex_external->goto_error_line()", "g", MODE); definekey("latex_external->goto_error_line()", "\n", MODE); definekey("latex_external->goto_error_line()", "\r", MODE); } %!%+ %\function{pop2compose_buf} %\synopsis{pops up a new window with the buffer for compiler output} %\usage{pop2compose_buf([Integer_Type goto_first_error])} %\description % If a buffer COMPILE_BUF_NAME exists, a new window is opened with the % buffer and this window is shrinked to a size of 6 rows. Therefor the % current line of the current window is moved in the upper half, that the % new window raises at the bottom. % % If the optional argument \var{goto_first_error} is given and != 0, the % cursor is moved to the first error. % % After all a short usage message is shown. % % The buffer is created by compose(). %\seealso{COMPILE_BUF_NAME, find_next_error(), compose()} %!%- static define pop_log_file() { variable log_file = path_sans_extname( get_master_file() ) + ".log"; !if ( show_log_file(log_file) ) return; use_keymap(MODE); set_buffer_hook("update_hook", &update_log_hook()); variable box=0, err=0, warn=0; bob(); while ( down(1) ) { switch ( what_char() ) { case 'L' or case 'P': if ( andelse {orelse {looking_at("LaTeX")} {looking_at("Package")} } {ffind("Warning:")}) { bol(); ++warn; } } { case 'O': if (looking_at("Overfull")) ++box; } { case 'U': if (looking_at("Underfull")) ++box; } { case '!': if (looking_at("! ")) ++err; } { case 'R': if (looking_at("Runaway argument?")) ++err; } } variable str = ""; if (err) str += string(err)+" errors, "; if (warn) str += string(warn)+" warnings, "; if (box) str += string(box)+" under-/overfull boxes, "; eob(); if ( andelse {bol_bsearch("Output written on")} {ffind_char('(')} {right(1)} ) { push_mark(); skip_chars("0-9"); str += bufsubstr() + " pages written"; } bob(); message(str); set_status_line(" "+path_basename(log_file) + " -- e: error; w: warning; b: box; g,Enter: go to error line; q: quit", 0); } private define next_log_line() { % TeX breakes lines at 80 characters. A line longer than this is more % than one real lines do eol(); while ( andelse {what_column() == 80} {down(1)} ); return down(1); } %!%+ %\function{goto_error_line} %\synopsis{jumps to the error line in the latex code} %\usage{goto_error_line} %\description % If the current editing point is in a line with an error or warning message % of latex, this function switches to the buffer of the file wherein the error % occured and jumps to the line, named in the output. %!%- static define goto_error_line() { variable start_mark = create_user_mark(), file_stack = {}; variable last_msg_type = '\0', last_msg_mark = start_mark; bob(); while (andelse {next_log_line()} {create_user_mark() <= start_mark} ) { switch ( what_char() ) { case 'L' or case 'P': push_mark(); () = ffind_char(':'); variable token = strtok(bufsubstr(), " "); if ( andelse {length(token) >= 2} {length(token) <= 3} {token[0] == "LaTeX" or token[0] == "Package"} {token[-1] == "Warning" or token[-1] == "Info"} ) { if (length(token) == 3) { variable pkg = "(" + token[1] + ")"; variable last_line_mark = create_user_mark(); while ( andelse {next_log_line()} {looking_at(pkg)} ) last_line_mark = create_user_mark(); goto_user_mark(last_line_mark); } last_msg_type = 'W'; last_msg_mark = create_user_mark(); continue; } } { case 'O' or case 'U': if (orelse {looking_at("Overfull ")} {looking_at("Underfull ")}) { () = ffind_char(' '); if ( looking_at(" \\hbox") ) { last_msg_type = 'B'; last_msg_mark = create_user_mark(); () = bol_fsearch(" []\n"); } continue; } } { case '!': if ( andelse {looking_at("! ")} {bol_fsearch("l.")} ) { last_msg_type = 'E'; last_msg_mark = create_user_mark(); () = bol_fsearch_char('\n'); continue; } } { case 'R': if ( looking_at("Runaway argument?") ) { last_msg_type = 'R'; last_msg_mark = create_user_mark(); () = bol_fsearch_char('\n'); continue; } } forever { skip_chars("^()\n"); switch ( what_char() ) { case '\n': if (what_column() != 80) break; % this is a wrapped line that is continued after the \n () = down(1); } { case ')': list_delete(file_stack, 0); () = right(1); } { case '(': () = right(1); push_mark(); skip_chars("^ \n\t(){"); while (what_column() == 80 and what_char() == '\n') { () = right(1); skip_chars("^ \n\t(){"); } list_insert(file_stack, str_delete_chars(bufsubstr(), "\n"), 0); } { case '\0': break; } % end of file } } goto_user_mark(last_msg_mark); variable line = 0, text, detail_text; switch (last_msg_type) { case 'W' or case 'B': variable log_msg = line_as_string(); while ( andelse {what_column() == 80} {down(1)} ) log_msg += line_as_string(); variable pos = is_substr(log_msg, "line"), dummy; if (pos != 0) () = sscanf(substr(log_msg, pos, strlen(log_msg)), "%s %d", &dummy, &line); } { case 'E': () = right(2); push_mark(); skip_chars("0-9"); line = integer( bufsubstr() ); () = right(1); push_mark(); eol(); text = bufsubstr(); bol(); if ( bol_bsearch("! ") ) { () = down(1); if ( looking_at("") ) { % For somewhat reason LaTeX prints out the text with % additional spaces. This might be the parsed form % (the tokens) of the text. eol(); bskip_chars(" "); push_mark(); bskip_chars("^ "); detail_text = bufsubstr(); } } } { case 'R': () = down(1); push_mark(); skip_chars("^\\"); text = bufsubstr(); forever { !if ( re_fsearch("^[debug_pos(); if ( looking_at("<*> ") ) { (text,) = strreplace(text, " ", "[ \n\t]*", strlen(text)); if ( ffind("\\input{") ) () = right(7); else () = right(4); push_mark(); skip_chars("^ \n\t(){}"); while (what_column() == 80 and what_char() == '\n') { () = right(1); skip_chars("^ \n\t(){}"); } list_insert(file_stack, bufsubstr(), 0); () = bol_fsearch("\n"); break; } else if ( looking_at("l.") ) { () = right(2); push_mark(); skip_chars("0-9"); line = integer( bufsubstr() ); detail_text = text; __uninitialize(&text); () = bol_fsearch("\n"); break; } } } goto_user_mark(start_mark); if (length(file_stack) == 0) throw UsageError, "You are not inside the scope of a LaTeX message"; variable file_name; if ( path_is_absolute(file_stack[0]) ) file_name = file_stack[0]; else file_name = path_concat(path_dirname(get_master_file()), file_stack[0]); call("delete_window"); if ( find_file(file_name) ) { if (line > 0) goto_line(line); if ( __is_initialized(&text) ) { if (line == 0) % This is a "Runaway argument" message { goto_line(1); () = re_fsearch(text); } else if ( strncmp(text, "...", 3) ) { variable col = strlen(text)+1; eol(); if ( col <= what_column() ) goto_column(col); else { % this happens when the file was edited bol(); __uninitialize(&detail_text); } } else () = right( ffind(text[[3:]]) ); } if ( __is_initialized(&detail_text) ) () = right( bsearch(detail_text) ); } } %%%%%%%%%% % % Composing % private define compose_sig_handl(pid, flags, status); private define compose_sig_handl(pid, flags, status) { if ( pid != get_master_var("compose_pid", -1) ) error("Notification for wrong pid got: ("+ string(pid) +","+ string(flags) +","+ string(status) + ")"); switch (flags) { case 1: return; } { case 4 or case 8: set_master_var(-1, "compose_pid"); if ( get_master_var("is_post_process", 0) ) { set_master_var(0, "is_post_process"); if (status != 0) throw ApplicationError, "(post process) finished with error"; message("(post process) finished"); return; } else { if (status != 0) throw ApplicationError, "(compose) finished with error"; flush("(compose) finished"); } variable master_file = get_master_file(), do_rerun = 0; if (cust[LaTeX_Config].rerun) { variable logfile = fopen(path_sans_extname(master_file)+".log", "r"); variable lines = fgetslines(logfile); () = fclose(logfile); % if one of the lines contain this string we must rerun latex do_rerun = any( array_map(Integer_Type, &is_substr(), lines, "Rerun to get cross-references right.")); % Fixme: "Marginpars may have changed." % "(mparhack) Rerun to get them right." } !if ( do_rerun or strlen(cust[LaTeX_Config].post) ) return; if (whatbuf() == " latex-compose") setbuf( find_buf_of_file(master_file) ); else { setbuf(" latex-compose"); create_blocal_var("LaTeX_master_file"); set_blocal_var(master_file, "LaTeX_master_file"); } variable cmd; if (do_rerun) { message("(compose) Rerun to get cross-references right..."); cmd = cust[LaTeX_Config].latex; } else { set_master_var(1, "is_post_process"); message("(compose) postprocessing output file..."); cmd = cust[LaTeX_Config].post; } pid = expand_and_run_cmd(cmd, DUMMY_OUTPUT_BUFFER); set_master_var(pid, "compose_pid"); set_process(pid, "signal", &compose_sig_handl()); } { error("Compose process exited abnormal: flag="+ string(flags) + ", status="+string(status)); } } static define compose() { if (get_master_var("compose_pid", -1) != -1) throw UsageError, "A compose process is already running"; call("save_some_buffers"); variable outp; if ( is_substr(cust[LaTeX_Config].latex, "rubber") ) outp = &output_to_msg(); else outp = DUMMY_OUTPUT_BUFFER; variable pid = expand_and_run_cmd(cust[LaTeX_Config].latex, outp); set_master_var(pid, "compose_pid"); set_process(pid, "signal", &compose_sig_handl()); message("Composing..."); } %!%+ %\function{cleanup} %\synopsis{removes all temporally files of latex} %\usage{cleanup()} %\description % Removes all temporally files, created by latex while composing. This files % are those which have the name of the compose file with a suffix defined % in the array Cleanup_extensions. % %\seealso{Cleanup_extensions} %!%- static define clearup() { message("Deleting temporary files..."); () = expand_and_run_cmd(cust[LaTeX_Config].clean, DUMMY_OUTPUT_BUFFER); } %!%+ %\function{mrproper} %\synopsis{removes all temporally files of latex and the output file} %\usage{mrproper()} %\description % Removes all temporally files, created by latex while composing. This files % are those which have the name of the compose file with a suffix defined % in the array Cleanup_extensions. % %\seealso{Cleanup_extensions} %!%- static define mrproper() { () = expand_and_run_cmd(cust[LaTeX_Config].mrproper, DUMMY_OUTPUT_BUFFER); message("Deleting temporary files and output file..."); } private variable server_comm = NULL, last_buf; private define view_comm_outp(pid, data) { if (strncmp("communicator created: ", data, 22) == 0) { server_comm = substr(data, 23, strlen(data)-23); sw2buf(last_buf); view(); return; } if (data == "quit\n") return; variable line, col, file; if (sscanf(data, "%d:%d%s", &line, &col, &file) != 3) throw ApplicationError, "View server send unknown string", data; () = find_file(file); goto_line(line); goto_column(col); #ifexists x_toggle_visibility x_toggle_visibility(0); #else tt_send("\e[5t"); % this is the control sequence to raise the % xterm to the front #endif } private define view_comm_sig(pid, flags, status) { switch (flags) { case 1: return; } { case 4 or case 8: server_comm = NULL; } { throw ApplicationError, "View server exited abnormal"; } } private define view_comm_quit() { if (server_comm == NULL) return; variable fifo = fopen(server_comm, "w"); try { () = fputs("quit\n", fifo); } finally () = fclose(fifo); % There's bug in SLang before version 2.0.7.91 that discards a system % call like fopen() if a signal arrives at the same time. Because we % use SLang for latex_comm.sl and it gets a SIGHUP when this process % finishes it never gets the quit. Due to this wait a moment and the % finish. sleep(.1); } add_to_hook("_jed_quit_hooks", &view_comm_quit()); static define view() { if (get_master_var("compose_pid", -1) != -1) throw UsageError, "A compose process is running. I cannot open viewer."; variable cmd = cust[LaTeX_Config].view; if ( is_substr(cmd, "%e") ) { variable latex_comm = expand_jedlib_file("latex_comm.sl"); if (server_comm == NULL) { last_buf = whatbuf(); variable pid = expand_and_run_cmd("jed-script $latex_comm watch"$, " latex-comm"); set_process(pid, "output", &view_comm_outp()); set_process(pid, "signal", &view_comm_sig()); process_query_at_exit(pid, 0); return; % the real viewer startup is triggered by view_comm_outp } (cmd,) = strreplace(cmd, "%e", "jed-script% "+ latex_comm +"% "+ server_comm + "% %%l:0%%c%%f", 1); } % Fixme: have a better idea than try bad(); catch: do_somthing_others(); % How to query is a process is assigned to the buffer? try { () = expand_and_run_cmd(cmd, " latex-view"); } catch RunTimeError: () = expand_and_run_cmd(cmd, " latex-view-2"); % xpdf -q -remote jlm-$PID -raise %M.pdf %p % xpdf -q -remote jlm-$PID -raise -reload % xpdf -q -remote jlm-$PID -quit } static define print() { if (get_master_var("compose_pid", -1) != -1) throw UsageError, "A compose process is running. I cannot start printer."; () = expand_and_run_cmd(cust[LaTeX_Config].print, &output_to_msg()); } %!%+ %\function{makeindex} %\synopsis{} %\usage{makeindex()} %\description % Don't know what it does. %!%- static define makeindex() { () = expand_and_run_cmd(cust[LaTeX_Config].index, DUMMY_OUTPUT_BUFFER); } static define show_mkidx_log() { () = show_log_file(path_sans_extname( get_master_file() ) + ".ilg"); } %!%+ %\function{bibtex} %\synopsis{} %\usage{bibtex()} %\description % Don't know what it does. %!%- static define bibtex() { () = expand_and_run_cmd(cust[LaTeX_Config].bibtex, DUMMY_OUTPUT_BUFFER); } static define show_bibtex_log() { () = show_log_file(path_sans_extname( get_master_file() ) + ".blg"); } provide("latex_external"); runhooks("after_latex_external_load_hook");