-- $Id$ -- view.tlu view a document and/or display the list of results in texdoc -- -- Manuel Pégourié-Gonnard, GPLv3+, see texdoclib.tlu for details ----------------------------- view a document ----------------------------- -- view a document -- see search.tlu for the structure of the argument function view_doc(docfile) return view_file(docfile.realpath) end -- get viewer and viewer_replacement before calling try_viewing -- returns false of failure, true on success -- viewer_replacement is either: -- 1. the filename, quoted with " -- 2. the filename, quoted with " followed by some rm commands -- The second case happens when the doc was zipped. In the case, this function -- unzips it in a tempdir so that the viewer command can use the unzipped file. function view_file (filename) local viewer, viewer_replacement -- check if the file is zipped local nozipname, zipext = parse_zip(filename) -- determine viewer_replacement if zipext then local unzip_cmd = config['unzip_'..zipext] if not unzip_cmd then err_print('error', "No unzip command for ."..zipext..' files, skipping '..filename) return false end local tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX") if not tmpdir then err_print('error', 'Failed to create tempdir to unzip.') return false end local basename = string.match(nozipname, '.*/(.*)$') or nozipname local tmpfile = '"'..tmpdir..'/'..basename..'"' local unzip = unzip_cmd..' "'..filename..'">'..tmpfile deb_print('view', "Unzip command: "..unzip) if not os.execute(unzip) then err_print('error', "Failed to unzip '"..filename.."'") os.remove(tmpfile) os.remove(tmpdir) return false end viewer_replacement = ''..tmpfile..'; ' ..config.rm_file..' '..tmpfile..'; ' ..config.rm_dir..' '..tmpdir filename = nozipname else viewer_replacement = '"'..w32_path(filename)..'"' end -- files without extension are assumed to be text local viewext = (filename:match('.*%.([^/]*)$') or 'txt'):lower() -- special case : sty files use txt viewer if viewext == 'sty' then viewext = 'txt' end -- FIXME: hardcoding such cases this way is not very clean if viewext == 'texlive' then viewext = 'txt' end if viewext == 'htm' then viewext = 'html' end -- get a viewer from built-in defaults if not already set if not config['viewer_'..viewext] then get_default_viewers() end -- still no viewers? use txt as a fallback if not config['viewer_'..viewext] then err_print('warning', "No viewer_"..viewext.." defined, using viewer_txt.") viewext = 'txt' end assert(config['viewer_'..viewext], 'Internal error: no viewer found.') return try_viewing(config['viewer_'..viewext], viewer_replacement) end -- view a file, if possible function try_viewing (view_command, viewer_replacement) if string.match (view_command, C.place_holder) then view_command = string.gsub( view_command, C.place_holder, viewer_replacement) else view_command = view_command..' '..viewer_replacement end -- try to catch problems with missing DISPLAY on Unix if os.type == 'unix' and not (os.name == 'macosx') and os.getenv('DISPLAY') == nil then err_print('warning', "DISPLAY is not set; your viewer will likely have problems.") err_print('warning', "Try --list to list results instead of displaying them.") end err_print('info', 'View command: '..view_command) -- See long comment below this function for the LC_CTYPE story. -- We only want to reset the environment if we have the value -- to reset it to. In older versions of luatex, status.lc_* will be nil. local env_lc_ctype = status.lc_ctype local luatex_lc_ctype = os.setlocale(nil, 'ctype') if (env_lc_ctype) then err_print('info', "Setting environment LC_CTYPE to: "..env_lc_ctype) os.setenv("LC_CTYPE", env_lc_ctype) end -- the big casino: run the external command. if not os.execute(view_command) then err_print('error', "Failed to execute: "..view_command) return false end -- reset back to "C" (should always be C and always happen, but in case...) if (luatex_lc_ctype) then os.setenv("LC_CTYPE", luatex_lc_ctype) end return true end -- Explanation of locale madness: -- LuaTeX resets LC_CTYPE, LC_COLLATE, LC_NUMERIC to "C". That is good for -- inside luatex, but when we run an external program, if the user's -- environment is something else, we want to switch back to it. As of -- TL 2017 LuaTeX, we can inspect the user's locale with status.lc_ctype, etc. -- -- For texdoc purposes, what matters is LC_CTYPE (so we don't bother with -- the others). For example, with the less pager, when LC_CTYPE=C, -- non-ASCII bytes are displayed as "", where xx is the two hex -- digits for the byte. -- -- See /tools/Makefile for a standalone test case. ----------------------------- display results ----------------------------- -- print a list of docfile objects (see search.tlu) as a menu -- if showall is false, stop as soon as a bad result is encountered function print_menu(name, doclist, showall) local max_lines = tonumber(config.max_lines) if config.interact_switch and doclist[max_lines+1] then -- there may be too many lines, count them local n if showall then n = #doclist else n = 0 while doclist[n+1] and doclist[n+1].quality == 'good' do n = n + 1 end end if n > max_lines then io.write (n, " results. Display them all? (y/N) ") local ans = io.read('*line') if not ((ans == 'y') or (ans == 'Y') -- io.read had a bug wrt windows eol on some versions of texlua or (ans == '\ry') or (ans == '\rY')) then return end end end local i, doc, last_i for i, doc in ipairs (doclist) do last_i = i -- save for test below if doc.quality == 'killed' then break end if doc.quality ~= 'good' and not showall then break end if config.machine_switch == true then print(name, doc.score, w32_path(doc.realpath), doc.lang or '', doc.details or '') else print(string.format('%2d %s', i, w32_path(doc.realpath))) if doc.details or doc.lang then local line = ' = ' if doc.lang then line = line..'['..doc.lang..'] ' end if doc.details then line = line..doc.details end print(line) end end end if config.interact_switch then io.write ("Enter number of file to view, ", "RET to view 1, anything else to skip: ") local num_str = io.read('*line') -- That returns the empty string on an empty line, nil on EOF. -- We only want to default to viewing 1 on an empty line. -- Use Lua's faked ternary operator for fun and brevity: num = (num_str == "" and 1 or tonumber(num_str)) if num and doclist[num] and num < last_i then view_doc(doclist[num]) end end end ----------------------- deliver results based on mode --------------------- function deliver_results(name, doclist, many) -- ensure that results were found or apologize if not doclist[1] or doclist[1].quality == 'killed' then if not config.machine_switch then local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name) print(msg) -- get rid of gsub's 2nd value end return end -- shall we show all of them or only the "good" ones? local showall = (config.mode == 'showall') if not showall and doclist[1].quality ~= 'good' then showall = true err_print('info', 'No good result found, showing all results.') end -- view result or show menu based on mode and number of results if (config.mode == 'view') or config.mode == 'mixed' and (not doclist[2] or (doclist[2].quality ~= 'good' and not showall)) then view_doc(doclist[1]) else if many and not config.machine_switch then print ("*** Results for: "..name.." ***") end print_menu(name, doclist, showall) end end return { view_file = view_file, deliver_results = deliver_results, }