diff --git a/README.md b/README.md index ed784c7..13fc506 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ and see the block like this in the output ## Customization -The extension defaults to running `xelatex` on the server. +The extension defaults to running the `xelatex` engine on the server. This command may be customized (e.g., to use `pdflatex` instead) by customizing your `jupyter_notebook_config.py` file: @@ -89,6 +89,8 @@ your `jupyter_notebook_config.py` file: c.LatexConfig.latex_command = 'pdflatex' ``` +The above configuration will compile a LaTeX document using the common predefined flags and options such as `-interaction` `-halt-on-error`, `-file-line-error`, `-synctex`. For more control over the command sequence, check the Manual Command Arguments configuration. + The extension defaults to running `bibtex` for generating a bibliography if a `.bib` file is found. You can also configure the bibliography command by setting @@ -97,6 +99,11 @@ by setting c.LatexConfig.bib_command = '' ``` +_New in 4.2.0_: `BibTeX` compilation is skipped if the following conditions are present: + +- `c.LatexConfig.disable_bibtex` is explicitly set to `True` in the `jupyter_notebook_config.py` file +- There are no .bib files found in the folder + To render references (`\ref{...}`), such as equation or chapter numbers, you would need to compile in multiple passes by setting @@ -104,6 +111,47 @@ need to compile in multiple passes by setting c.LatexConfig.run_times = 2 ``` +_New in 4.2.0_: Manual Compile Command +For more advanced customizations, a complete command sequence can be specified using the `manual_cmd_args` configuration in the `jupyter_notebook_config.py` file. This allows to define the exact command and use options the extension will finally execute: + +```python +c.LatexConfig.manual_cmd_args = [ + 'lualatex', # Specify the LaTeX engine (e.g., lualatex, pdflatex) + '-interaction=nonstopmode', # Continue compilation without stopping for errors + '-halt-on-error', # Stop compilation at the first error + '-file-line-error', # Print file and line number for errors + '-shell-escape', # Enable shell escape + '-synctex=1', # Enable SyncTeX for editor synchronization + '{filename}.tex' # Placeholder for the input file name +] +``` + +The only supported placeholder in the manual compile command is `{filename}`. It will be replaced by the name of the LaTeX file during compilation. + +Additional tags and options can also be added to edit configuration values. + +_New in 4.2.0_: Tectonic Engine Support +The extension now also supports the Tectonic engine for compiling LaTeX files. To use Tectonic as the default LaTeX engine cutomize the `jupyter_notebook_config.py file`: + +```python +c.LatexConfig.latex_command = 'tectonic' +``` + +The default command sequence for Tectonic generates the output file in `pdf` format and uses the available `SyncTeX` flag. + +For [advanced control](https://tectonic-typesetting.github.io/book/latest/v2cli/compile.html) over [Tectonic](https://github.com/tectonic-typesetting/tectonic), specify options in the manual_cmd_args setting: + +```python +c.LatexConfig.manual_cmd_args = [ + 'tectonic', + '--outfmt=pdf', # Output format as PDF + '--synctex=1', # Enable SyncTeX for editor synchronization + '--outdir', # The directory in which to place output files + '--keep-logs', # Keep the log files generated during processing + '{filename}.tex' # Input .tex file +] +``` + ### Security and customizing shell escapes LaTeX files have the ability to run arbitrary code by triggering external diff --git a/jupyterlab_latex/build.py b/jupyterlab_latex/build.py index 713340d..691ffdc 100644 --- a/jupyterlab_latex/build.py +++ b/jupyterlab_latex/build.py @@ -68,7 +68,7 @@ def initialize(self, root_dir): self.root_dir = root_dir - def build_tex_cmd_sequence(self, tex_base_name, run_bibtex=False): + def build_tex_cmd_sequence(self, tex_base_name): """Builds tuples that will be used to call LaTeX shell commands. Parameters @@ -83,6 +83,8 @@ def build_tex_cmd_sequence(self, tex_base_name, run_bibtex=False): """ c = LatexConfig(config=self.config) + + engine_name = c.latex_command escape_flag = '' if c.shell_escape == 'allow': @@ -94,35 +96,57 @@ def build_tex_cmd_sequence(self, tex_base_name, run_bibtex=False): # Get the synctex query parameter, defaulting to # 1 if it is not set or is invalid. - synctex = self.get_query_argument('synctex', default='1') - synctex = '1' if synctex != '0' else synctex - - full_latex_sequence = ( - c.latex_command, - escape_flag, - "-interaction=nonstopmode", - "-halt-on-error", - "-file-line-error", - f"-synctex={synctex}", - f"{tex_base_name}", + synctex = self.get_query_argument('synctex', default=True) + + if c.manual_cmd_args: + # Replace placeholders with actual values + self.log.info("Using the manual command argument and buidling latex sequence.") + full_latex_sequence = [ + # replace placeholders using format() + arg.format(filename=tex_base_name) + for arg in c.manual_cmd_args + ] + elif engine_name == 'tectonic': + self.log.info("Using Tectonic for LaTeX compilation.") + full_latex_sequence = ( + engine_name, + f"{tex_base_name}.tex", # input .tex file + "--outfmt=pdf", # specify output format (pdf in this case) + f"--synctex={'1' if synctex else '0'}" # to support SyncTeX (synchronization with the editor) ) - - full_bibtex_sequence = ( - c.bib_command, - f"{tex_base_name}", + else: + self.log.info("Using TeX Live (or compatible distribution) for LaTeX compilation.") + full_latex_sequence = ( + engine_name, + escape_flag, + "-interaction=nonstopmode", + "-halt-on-error", + "-file-line-error", + f"-synctex={'1' if synctex else '0'}", + f"{tex_base_name}", ) - + command_sequence = [full_latex_sequence] - if run_bibtex: + # Skip bibtex compilation if the following conditions are present + # - c.LatexConfig.disable_bibtex is explicitly set to True + # - tectonic engine is used + # - there are no .bib files found in the folder + if c.disable_bibtex or engine_name == 'tectonic' or not self.bib_condition(): + # Repeat LaTeX command run_times times + command_sequence = command_sequence * c.run_times + else: + full_bibtex_sequence = ( + c.bib_command, + f"{tex_base_name}", + ) + command_sequence += [ full_bibtex_sequence, full_latex_sequence, full_latex_sequence, - ] - else: - command_sequence = command_sequence * c.run_times - + ] + return command_sequence def bib_condition(self): diff --git a/jupyterlab_latex/config.py b/jupyterlab_latex/config.py index 7bfb767..c4f0e6e 100644 --- a/jupyterlab_latex/config.py +++ b/jupyterlab_latex/config.py @@ -1,6 +1,6 @@ """ JupyterLab LaTex : live LaTeX editing for JupyterLab """ -from traitlets import Unicode, CaselessStrEnum, Integer, Bool +from traitlets import Unicode, CaselessStrEnum, Integer, Bool, List as TraitletsList from traitlets.config import Configurable class LatexConfig(Configurable): @@ -10,10 +10,13 @@ class LatexConfig(Configurable): """ latex_command = Unicode('xelatex', config=True, help='The LaTeX command to use when compiling ".tex" files.') + disable_bibtex = Bool(default_value=False, config=True, + help='Whether to disable the BibTeX command sequence.') bib_command = Unicode('bibtex', config=True, - help='The BibTeX command to use when compiling ".tex" files.') - synctex_command = Unicode('synctex', config=True, - help='The synctex command to use when syncronizing between .tex and .pdf files.') + help='The BibTeX command to use when compiling ".tex" files.' +\ + 'Only used if disable_bibtex is not set to True') + synctex_command = Bool('synctex', config=True, + help='Whether to use the synctex command when syncronizing between .tex and .pdf files.') shell_escape = CaselessStrEnum(['restricted', 'allow', 'disallow'], default_value='restricted', config=True, help='Whether to allow shell escapes '+\ @@ -25,3 +28,7 @@ class LatexConfig(Configurable): help='How many times to compile the ".tex" files.') cleanup = Bool(default_value=True, config=True, help='Whether to clean up ".out/.aux" files or not.') + # Add a new configuration option to hold user-defined commands + manual_cmd_args = TraitletsList(Unicode(), default_value=[], config=True, + help='A list of user-defined command-line arguments with placeholders for ' + + 'filename ({filename})')