| Author | DaveJarvis <email> |
|---|---|
| Date | 2021-03-30 23:36:57 GMT-0700 |
| Commit | f9c1a4878dfff7cce102f953e5f341f58a4e8346 |
| Parent | 7b2f511 |
| Delta | 262 lines added, 130 lines removed, 132-line increase |
|---|
| public ProcessorContext createProcessorContext( | ||
| - final File exportPath, final ExportFormat format ) { | ||
| + final Path exportPath, final ExportFormat format ) { | ||
| final var editor = getActiveTextEditor(); | ||
| return createProcessorContext( | ||
| - editor.getPath(), editor.getCaret(), exportPath, format ); | ||
| + editor.getPath(), exportPath, format, editor.getCaret() ); | ||
| } | ||
| private ProcessorContext createProcessorContext( | ||
| final Path path, final Caret caret ) { | ||
| - return createProcessorContext( path, caret, null, ExportFormat.NONE ); | ||
| + return createProcessorContext( path, null, ExportFormat.NONE, caret ); | ||
| } | ||
| /** | ||
| * @param path Used by {@link ProcessorFactory} to determine | ||
| * {@link Processor} type to create based on file type. | ||
| - * @param caret Used by {@link CaretExtension} to add ID attribute into | ||
| - * preview document for scrollbar synchronization. | ||
| * @param exportPath Used when exporting to a PDF file (binary). | ||
| * @param format Used when processors export to a new text format. | ||
| + * @param caret Used by {@link CaretExtension} to add ID attribute into | ||
| + * preview document for scrollbar synchronization. | ||
| * @return A new {@link ProcessorContext} to use when creating an instance of | ||
| * {@link Processor}. | ||
| */ | ||
| private ProcessorContext createProcessorContext( | ||
| - final Path path, final Caret caret, | ||
| - final File exportPath, final ExportFormat format ) { | ||
| + final Path path, final Path exportPath, final ExportFormat format, | ||
| + final Caret caret ) { | ||
| return new ProcessorContext( | ||
| - mPreview, mResolvedMap, path, caret, exportPath, format, mWorkspace | ||
| + mPreview, mResolvedMap, path, exportPath, format, mWorkspace, caret | ||
| ); | ||
| } |
| TEXT_R_MARKDOWN( TEXT, "R+markdown" ), | ||
| TEXT_R_XML( TEXT, "R+xml" ), | ||
| + TEXT_XHTML( TEXT, "xhtml+xml" ), | ||
| + TEXT_XML( TEXT, "xml" ), | ||
| TEXT_YAML( TEXT, "yaml" ), | ||
| */ | ||
| public boolean isSvg() { | ||
| - return this == IMAGE_SVG_XML; | ||
| + // Kroki serves HTTP HEAD requests back as text/plain for SVG images. | ||
| + return this == IMAGE_SVG_XML || this == TEXT_PLAIN; | ||
| } | ||
| MEDIA_TEXT_R_MARKDOWN( TEXT_R_MARKDOWN, of( "Rmd" ) ), | ||
| MEDIA_TEXT_R_XML( TEXT_R_XML, of( "Rxml" ) ), | ||
| + MEDIA_TEXT_XHTML( TEXT_XHTML, of( "xhtml" ) ), | ||
| + MEDIA_TEXT_XML( TEXT_XML ), | ||
| MEDIA_TEXT_YAML( TEXT_YAML, of( "yaml", "yml" ) ), | ||
| localeProperty( KEY_LANGUAGE_LOCALE ) ) | ||
| ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_TYPESET ), | ||
| + Group.of( | ||
| + get( KEY_TYPESET_CONTEXT ), | ||
| + Setting.of( label( KEY_TYPESET_CONTEXT_PATH ) ), | ||
| + Setting.of( title( KEY_TYPESET_CONTEXT_PATH ), | ||
| + stringProperty( KEY_TYPESET_CONTEXT_PATH ) ), | ||
| + Setting.of( label( KEY_TYPESET_CONTEXT_ENV ) ), | ||
| + Setting.of( title( KEY_TYPESET_CONTEXT_ENV ), | ||
| + stringProperty( KEY_TYPESET_CONTEXT_ENV ) ) | ||
| + ) | ||
| ) | ||
| ).instantPersistent( false ).dialogIcon( ICON_DIALOG ); |
| entry( KEY_UI_THEME_CUSTOM, asFileProperty( THEME_CUSTOM_DEFAULT ) ), | ||
| - entry( KEY_LANGUAGE_LOCALE, asLocaleProperty( LOCALE_DEFAULT ) ) | ||
| - ); | ||
| + entry( KEY_LANGUAGE_LOCALE, asLocaleProperty( LOCALE_DEFAULT ) ), | ||
| + | ||
| + entry( KEY_TYPESET_CONTEXT_PATH, asStringProperty( USER_DIRECTORY.toString() ) ), | ||
| + entry( KEY_TYPESET_CONTEXT_ENV, asStringProperty( "" ) ) | ||
| + ); | ||
| //@formatter:on | ||
| public static final Key KEY_UI_THEME_CUSTOM = key( KEY_UI_THEME, "custom" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM = key( KEY_UI_THEME, "custom" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_FONT = key( KEY_UI_THEME_CUSTOM, "font" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_FONT_SIZE = key( KEY_UI_THEME_CUSTOM_FONT, "size" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS = key( KEY_UI_THEME_CUSTOM, "colours" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_BASE = key( KEY_UI_THEME_CUSTOM_COLOURS, "base" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_BG = key( KEY_UI_THEME_CUSTOM_COLOURS, "background" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_CONTROLS = key( KEY_UI_THEME_CUSTOM_COLOURS, "controls" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_ROW1 = key( KEY_UI_THEME_CUSTOM_COLOURS, "row" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_ROW2 = key( KEY_UI_THEME_CUSTOM_COLOURS, "row" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_FG = key( KEY_UI_THEME_CUSTOM_COLOURS, "foreground" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_FG_LIGHT = key( KEY_UI_THEME_CUSTOM_COLOURS_FG, "light" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_FG_MEDIUM = key( KEY_UI_THEME_CUSTOM_COLOURS_FG, "medium" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_FG_DARK = key( KEY_UI_THEME_CUSTOM_COLOURS_FG, "dark" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_ACCENT = key( KEY_UI_THEME_CUSTOM_COLOURS, "accent" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_UNFOCUSED = key( KEY_UI_THEME_CUSTOM_COLOURS, "unfocused" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR = key( KEY_UI_THEME_CUSTOM_COLOURS, "scrollbar" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON = key( KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR, "button" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON_RELEASED = key( KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON, "released" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON_PRESSED = key( KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON, "pressed" ); | ||
| -// public static final Key KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON_HOVER = key( KEY_UI_THEME_CUSTOM_COLOURS_SCROLLBAR_BUTTON, "hover" ); | ||
| - | ||
| public static final Key KEY_LANGUAGE = key( KEY_ROOT, "language" ); | ||
| public static final Key KEY_LANGUAGE_LOCALE = key( KEY_LANGUAGE, "locale" ); | ||
| + | ||
| + public static final Key KEY_TYPESET = key( KEY_ROOT, "typeset" ); | ||
| + public static final Key KEY_TYPESET_CONTEXT = key( KEY_TYPESET, "context" ); | ||
| + public static final Key KEY_TYPESET_CONTEXT_PATH = key( KEY_TYPESET_CONTEXT, "path" ); | ||
| + public static final Key KEY_TYPESET_CONTEXT_ENV = key( KEY_TYPESET_CONTEXT, "environment" ); | ||
| //@formatter:on | ||
| /** | ||
| - * | ||
| + * Only for constants, do not instantiate. | ||
| */ | ||
| private WorkspaceKeys() { } |
| import com.keenwrite.typesetting.Typesetter; | ||
| -import java.io.File; | ||
| -import java.io.IOException; | ||
| - | ||
| import static com.keenwrite.events.StatusEvent.clue; | ||
| -import static com.keenwrite.io.MediaType.APP_PDF; | ||
| +import static com.keenwrite.io.MediaType.TEXT_XML; | ||
| import static com.keenwrite.util.FileUtils.createTemporaryFile; | ||
| +import static java.nio.file.Files.writeString; | ||
| /** | ||
| * Responsible for using a typesetting engine to convert an XHTML document | ||
| * into a PDF file. | ||
| */ | ||
| public final class PdfProcessor extends ExecutorProcessor<String> { | ||
| - private static final Typesetter sTypesetter = new Typesetter(); | ||
| - private final File mExportPath; | ||
| + private final ProcessorContext mContext; | ||
| - public PdfProcessor( final File exportPath ) { | ||
| - assert exportPath != null; | ||
| - mExportPath = exportPath; | ||
| + public PdfProcessor( final ProcessorContext context ) { | ||
| + assert context != null; | ||
| + mContext = context; | ||
| } | ||
| public String apply( final String xhtml ) { | ||
| try { | ||
| - final var document = createTemporaryFile( APP_PDF ); | ||
| - sTypesetter.typeset( document, mExportPath ); | ||
| - } catch( final IOException ex ) { | ||
| + final var sTypesetter = new Typesetter( mContext.getWorkspace() ); | ||
| + final var document = createTemporaryFile( TEXT_XML ); | ||
| + final var exportPath = mContext.getExportPath(); | ||
| + sTypesetter.typeset( writeString( document, xhtml ), exportPath ); | ||
| + } catch( final Exception ex ) { | ||
| clue( ex ); | ||
| } | ||
| + // Do not continue processing (the document was typeset into a binary). | ||
| return null; | ||
| } | ||
| import com.keenwrite.preview.HtmlPreview; | ||
| -import java.io.File; | ||
| import java.nio.file.Path; | ||
| import java.util.Map; | ||
| private final Map<String, String> mResolvedMap; | ||
| private final Path mDocumentPath; | ||
| + private final Path mExportPath; | ||
| private final Caret mCaret; | ||
| - private final File mExportPath; | ||
| private final ExportFormat mExportFormat; | ||
| private final Workspace mWorkspace; | ||
| * @param resolvedMap Fully expanded interpolated strings. | ||
| * @param documentPath Path to the document to process. | ||
| - * @param caret Location of the caret in the edited document, which is | ||
| - * used to synchronize the scrollbars. | ||
| * @param exportPath Fully qualified filename to use when exporting. | ||
| * @param exportFormat Indicate configuration options for export format. | ||
| * @param workspace Persistent user preferences settings. | ||
| + * @param caret Location of the caret in the edited document, which is | ||
| + * used to synchronize the scrollbars. | ||
| */ | ||
| public ProcessorContext( | ||
| final HtmlPreview htmlPreview, | ||
| final Map<String, String> resolvedMap, | ||
| final Path documentPath, | ||
| - final Caret caret, | ||
| - final File exportPath, | ||
| + final Path exportPath, | ||
| final ExportFormat exportFormat, | ||
| - final Workspace workspace ) { | ||
| + final Workspace workspace, | ||
| + final Caret caret ) { | ||
| assert htmlPreview != null; | ||
| assert resolvedMap != null; | ||
| assert documentPath != null; | ||
| - assert caret != null; | ||
| assert exportFormat != null; | ||
| assert workspace != null; | ||
| + assert caret != null; | ||
| mHtmlPreview = htmlPreview; | ||
| * @return Full path to a file name. | ||
| */ | ||
| - public File getExportPath() { | ||
| + public Path getExportPath() { | ||
| return mExportPath; | ||
| } | ||
| private Processor<String> createPdfProcessor( | ||
| final ProcessorContext context ) { | ||
| - final var pdfp = new PdfProcessor( context.getExportPath() ); | ||
| + final var pdfp = new PdfProcessor( context ); | ||
| return createXhtmlProcessor( pdfp, context ); | ||
| } |
| package com.keenwrite.typesetting; | ||
| -import java.io.File; | ||
| +import com.keenwrite.preferences.Key; | ||
| +import com.keenwrite.preferences.Workspace; | ||
| + | ||
| +import java.io.IOException; | ||
| import java.nio.file.Path; | ||
| +import java.util.ArrayList; | ||
| +import java.util.List; | ||
| +import java.util.concurrent.Callable; | ||
| +import java.util.concurrent.ExecutorService; | ||
| +import static com.keenwrite.Constants.DEFAULT_DIRECTORY; | ||
| +import static com.keenwrite.Messages.get; | ||
| +import static com.keenwrite.events.StatusEvent.clue; | ||
| +import static com.keenwrite.preferences.WorkspaceKeys.KEY_TYPESET_CONTEXT_ENV; | ||
| +import static com.keenwrite.preferences.WorkspaceKeys.KEY_TYPESET_CONTEXT_PATH; | ||
| import static com.keenwrite.util.FileUtils.canExecute; | ||
| +import static java.lang.String.format; | ||
| +import static java.lang.System.currentTimeMillis; | ||
| +import static java.util.concurrent.Executors.newFixedThreadPool; | ||
| +import static java.util.concurrent.TimeUnit.*; | ||
| /** | ||
| * Represents the executable responsible for typesetting text. This will | ||
| * construct suitable command-line arguments to invoke the typesetting engine. | ||
| */ | ||
| public class Typesetter { | ||
| private static final String TYPESETTER = "context"; | ||
| - public Typesetter() { | ||
| - } | ||
| + private static final ExecutorService sService = newFixedThreadPool( 5 ); | ||
| - public boolean isInstalled() { | ||
| - return canExecute( TYPESETTER ); | ||
| + private final Workspace mWorkspace; | ||
| + | ||
| + /** | ||
| + * Creates a new {@link Typesetter} instance capable of configuring the | ||
| + * typesetter used to generate a typeset document. | ||
| + */ | ||
| + public Typesetter( final Workspace workspace ) { | ||
| + mWorkspace = workspace; | ||
| } | ||
| /** | ||
| * This will typeset the document using a new process. | ||
| * | ||
| * @param input The input document to typeset. | ||
| * @param output Path to the finished typeset document. | ||
| */ | ||
| - public void typeset( final Path input, final File output ) { | ||
| + public void typeset( final Path input, final Path output ) | ||
| + throws IOException { | ||
| + if( isInstalled() ) { | ||
| + sService.submit( new TypesetTask( input, output ) ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Launches a task to typeset a document. | ||
| + */ | ||
| + public class TypesetTask implements Callable<Integer> { | ||
| + private final List<String> mArgs = new ArrayList<>(); | ||
| + | ||
| + /** | ||
| + * Working directory must be set because ConTeXt cannot write the | ||
| + * result to an arbitrary location. | ||
| + */ | ||
| + private final Path mDirectory; | ||
| + | ||
| + /** | ||
| + * Fully qualified destination file name. | ||
| + */ | ||
| + private final Path mOutput; | ||
| + | ||
| + public TypesetTask( final Path input, final Path output ) { | ||
| + final var filename = output.getFileName(); | ||
| + final var parentDir = output.getParent(); | ||
| + mDirectory = (parentDir == null ? DEFAULT_DIRECTORY : parentDir); | ||
| + mOutput = output; | ||
| + | ||
| + final var paths = getProperty( KEY_TYPESET_CONTEXT_PATH ); | ||
| + final var envs = getProperty( KEY_TYPESET_CONTEXT_ENV ); | ||
| + | ||
| + mArgs.add( TYPESETTER ); | ||
| + mArgs.add( "--batchmode" ); | ||
| + mArgs.add( "--purgeall" ); | ||
| + mArgs.add( "--path='" + paths + "'" ); | ||
| + mArgs.add( "--environment='" + envs + "'" ); | ||
| + mArgs.add( "--result='" + filename + "'" ); | ||
| + mArgs.add( input.toString() ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public Integer call() throws Exception { | ||
| + final var elapsed = currentTimeMillis(); | ||
| + final var output = mOutput.toString(); | ||
| + clue( get( "Main.status.typeset.began", output ) ); | ||
| + | ||
| + final var builder = new ProcessBuilder( mArgs ); | ||
| + builder.directory( mDirectory.toFile() ); | ||
| + final var process = builder.start(); | ||
| + process.waitFor(); | ||
| + | ||
| + final var code = process.exitValue(); | ||
| + final var time = asElapsed( currentTimeMillis() - elapsed ); | ||
| + clue( | ||
| + code == 0 | ||
| + ? get( "Main.status.typeset.ended.success", output, time ) | ||
| + : get( "Main.status.typeset.ended.failure", output, time, code ) | ||
| + ); | ||
| + | ||
| + return code; | ||
| + } | ||
| + } | ||
| + | ||
| + private String getProperty( final Key key ) { | ||
| + return mWorkspace.stringProperty( key ).toString(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helps ensure that the typesetting software can be run. | ||
| + * | ||
| + * @return {@code true} when the typesetting software is available. | ||
| + */ | ||
| + private boolean isInstalled() { | ||
| + return canExecute( TYPESETTER ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Converts an elapsed time to a human-readable format (hours, minutes, | ||
| + * seconds, and milliseconds). | ||
| + * | ||
| + * @param elapsed An elapsed time, in milliseconds. | ||
| + * @return Human-readable elapsed time. | ||
| + */ | ||
| + private static String asElapsed( final long elapsed ) { | ||
| + final var hours = MILLISECONDS.toHours( elapsed ); | ||
| + final var eHours = elapsed - HOURS.toMillis( hours ); | ||
| + final var minutes = MILLISECONDS.toMinutes( eHours ); | ||
| + final var eMinutes = eHours - MINUTES.toMillis( minutes ); | ||
| + final var seconds = MILLISECONDS.toSeconds( eMinutes ); | ||
| + final var eSeconds = eMinutes - SECONDS.toMillis( seconds ); | ||
| + final var milliseconds = MILLISECONDS.toMillis( eSeconds ); | ||
| + return format( "%02d:%02d:%02d.%03d", | ||
| + hours, minutes, seconds, milliseconds ); | ||
| } | ||
| } |
| selection.ifPresent( ( file ) -> { | ||
| final var doc = editor.getText(); | ||
| - final var context = main.createProcessorContext( file, format ); | ||
| + final var context = main.createProcessorContext( file.toPath(), format ); | ||
| final var chain = createProcessors( context ); | ||
| final var export = chain.apply( doc ); | ||
| try { | ||
| // Processors can export in binary formats that are incompatible with | ||
| // Java language String objects. In such cases, the processor will | ||
| // return the null sentinel to signal no further processing is needed. | ||
| if( export != null ) { | ||
| writeString( file.toPath(), export ); | ||
| - } | ||
| - clue( get( "Main.status.export.success", file.toString() ) ); | ||
| + // Binary formats must notify users of success independently. | ||
| + clue( get( "Main.status.export.success", file.toString() ) ); | ||
| + } | ||
| } catch( final Exception ex ) { | ||
| clue( ex ); |
| # ######################################################################## | ||
| -# Menu Bar | ||
| -# ######################################################################## | ||
| - | ||
| -Main.menu.file=_File | ||
| -Main.menu.edit=_Edit | ||
| -Main.menu.insert=_Insert | ||
| -Main.menu.format=Forma_t | ||
| -Main.menu.definition=_Variable | ||
| -Main.menu.view=Vie_w | ||
| -Main.menu.help=_Help | ||
| - | ||
| -# ######################################################################## | ||
| -# Detachable Tabs | ||
| -# ######################################################################## | ||
| - | ||
| -# {0} is the application title; {1} is a unique window ID. | ||
| -Detach.tab.title={0} - {1} | ||
| - | ||
| -# ######################################################################## | ||
| -# Status Bar | ||
| -# ######################################################################## | ||
| - | ||
| -Main.status.text.offset=offset | ||
| -Main.status.line=Line {0} of {1}, ${Main.status.text.offset} {2} | ||
| -Main.status.state.default=OK | ||
| -Main.status.export.success=Saved as {0} | ||
| - | ||
| -Main.status.error.bootstrap.eval=Note: Bootstrap variable of ''{0}'' not found | ||
| - | ||
| -Main.status.error.parse={0} (near ${Main.status.text.offset} {1}) | ||
| -Main.status.error.def.blank=Move the caret to a word before inserting a variable | ||
| -Main.status.error.def.empty=Create a variable before inserting one | ||
| -Main.status.error.def.missing=No variable value found for ''{0}'' | ||
| -Main.status.error.r=Error with [{0}...]: {1} | ||
| -Main.status.error.file.missing=Not found: {0} | ||
| - | ||
| -Main.status.error.messages.recursion=Lookup depth exceeded, check for loops in ''{0}'' | ||
| -Main.status.error.messages.syntax=Missing ''}'' in ''{0}'' | ||
| - | ||
| -Main.status.error.undo=Cannot undo; beginning of undo history reached | ||
| -Main.status.error.redo=Cannot redo; end of redo history reached | ||
| - | ||
| -Main.status.image.request.init=Initializing HTTP request | ||
| -Main.status.image.request.fetch=Requesting content type from {0} | ||
| -Main.status.image.request.success=Determined content type ''{0}'' | ||
| -Main.status.image.request.error.media=No media type for ''{0}'' | ||
| -Main.status.image.request.error.cert=Could not accept certificate for ''{0}'' | ||
| - | ||
| -Main.status.font.search.missing=No font name starting with ''{0}'' was found | ||
| - | ||
| -# ######################################################################## | ||
| -# Search Bar | ||
| -# ######################################################################## | ||
| - | ||
| -Main.search.stop.tooltip=Close search bar | ||
| -Main.search.stop.icon=CLOSE | ||
| -Main.search.next.tooltip=Find next match | ||
| -Main.search.next.icon=CHEVRON_DOWN | ||
| -Main.search.prev.tooltip=Find previous match | ||
| -Main.search.prev.icon=CHEVRON_UP | ||
| -Main.search.find.tooltip=Search document for text | ||
| -Main.search.find.icon=SEARCH | ||
| -Main.search.match.none=No matches | ||
| -Main.search.match.some={0} of {1} matches | ||
| - | ||
| -# ######################################################################## | ||
| # Workspace preferences | ||
| # ######################################################################## | ||
| workspace.language.locale.desc=Language for application and HTML export. | ||
| workspace.language.locale.title=Locale | ||
| + | ||
| +workspace.typeset=Typesetting | ||
| +workspace.typeset.context=ConTeXt | ||
| +workspace.typeset.context.path=Paths | ||
| +workspace.typeset.context.path.desc=Comma-separated directories containing support files | ||
| +workspace.typeset.context.path.title=Directories | ||
| +workspace.typeset.context.environment=Environments | ||
| +workspace.typeset.context.environment.desc=Comma-separated environment file names | ||
| +workspace.typeset.context.environment.title=Files | ||
| + | ||
| +# ######################################################################## | ||
| +# Menu Bar | ||
| +# ######################################################################## | ||
| + | ||
| +Main.menu.file=_File | ||
| +Main.menu.edit=_Edit | ||
| +Main.menu.insert=_Insert | ||
| +Main.menu.format=Forma_t | ||
| +Main.menu.definition=_Variable | ||
| +Main.menu.view=Vie_w | ||
| +Main.menu.help=_Help | ||
| + | ||
| +# ######################################################################## | ||
| +# Detachable Tabs | ||
| +# ######################################################################## | ||
| + | ||
| +# {0} is the application title; {1} is a unique window ID. | ||
| +Detach.tab.title={0} - {1} | ||
| + | ||
| +# ######################################################################## | ||
| +# Status Bar | ||
| +# ######################################################################## | ||
| + | ||
| +Main.status.text.offset=offset | ||
| +Main.status.line=Line {0} of {1}, ${Main.status.text.offset} {2} | ||
| +Main.status.state.default=OK | ||
| +Main.status.export.success=Saved as ''{0}'' | ||
| + | ||
| +Main.status.error.bootstrap.eval=Note: Bootstrap variable of ''{0}'' not found | ||
| + | ||
| +Main.status.error.parse={0} (near ${Main.status.text.offset} {1}) | ||
| +Main.status.error.def.blank=Move the caret to a word before inserting a variable | ||
| +Main.status.error.def.empty=Create a variable before inserting one | ||
| +Main.status.error.def.missing=No variable value found for ''{0}'' | ||
| +Main.status.error.r=Error with [{0}...]: {1} | ||
| +Main.status.error.file.missing=Not found: ''{0}'' | ||
| + | ||
| +Main.status.error.messages.recursion=Lookup depth exceeded, check for loops in ''{0}'' | ||
| +Main.status.error.messages.syntax=Missing ''}'' in ''{0}'' | ||
| + | ||
| +Main.status.error.undo=Cannot undo; beginning of undo history reached | ||
| +Main.status.error.redo=Cannot redo; end of redo history reached | ||
| + | ||
| +Main.status.image.request.init=Initializing HTTP request | ||
| +Main.status.image.request.fetch=Requesting content type from ''{0}'' | ||
| +Main.status.image.request.success=Determined content type ''{0}'' | ||
| +Main.status.image.request.error.media=No media type for ''{0}'' | ||
| +Main.status.image.request.error.cert=Could not accept certificate for ''{0}'' | ||
| + | ||
| +Main.status.typeset.began=Started typesetting ''{0}'' | ||
| +Main.status.typeset.ended.success=Finished typesetting ''{0}'' ({1} elapsed) | ||
| +Main.status.typeset.ended.failure=Failed to typeset ''{0}'' ({1} elapsed); exit code: {2} | ||
| + | ||
| +Main.status.font.search.missing=No font name starting with ''{0}'' was found | ||
| + | ||
| +# ######################################################################## | ||
| +# Search Bar | ||
| +# ######################################################################## | ||
| + | ||
| +Main.search.stop.tooltip=Close search bar | ||
| +Main.search.stop.icon=CLOSE | ||
| +Main.search.next.tooltip=Find next match | ||
| +Main.search.next.icon=CHEVRON_DOWN | ||
| +Main.search.prev.tooltip=Find previous match | ||
| +Main.search.prev.icon=CHEVRON_UP | ||
| +Main.search.find.tooltip=Search document for text | ||
| +Main.search.find.icon=SEARCH | ||
| +Main.search.match.none=No matches | ||
| +Main.search.match.some={0} of {1} matches | ||
| # ######################################################################## | ||