22
33import os
44from itertools import groupby , count
5- from operator import itemgetter
65import json
76
8- import sphinx
9- from sphinx .util import logging
7+ from sphinx .util import logging , parselinenos
108from sphinx .util .fileutil import copy_asset
119from sphinx .transforms import SphinxTransform
1210from sphinx .errors import ExtensionError
2624
2725import nbformat
2826
29- from ipywidgets import Widget
3027import ipywidgets .embed
3128
3229from ._version import __version__
@@ -136,6 +133,8 @@ class JupyterCell(Directive):
136133 If provided, the code will be shown below the cell output.
137134 linenos : bool
138135 If provided, the code will be shown with line numbering.
136+ emphasize-lines : comma separated list of line numbers
137+ If provided, the specified lines will be highlighted.
139138 raises : comma separated list of exception types
140139 If provided, a comma-separated list of exception type names that
141140 the cell may raise. If one of the listed execption types is raised
@@ -159,11 +158,14 @@ class JupyterCell(Directive):
159158 'hide-output' : directives .flag ,
160159 'code-below' : directives .flag ,
161160 'linenos' : directives .flag ,
161+ 'emphasize-lines' : directives .unchanged_required ,
162162 'raises' : csv_option ,
163163 'stderr' : directives .flag ,
164164 }
165165
166166 def run (self ):
167+ location = self .state_machine .get_source_and_line (self .lineno )
168+
167169 if self .arguments :
168170 # As per 'sphinx.directives.code.LiteralInclude'
169171 env = self .state .document .settings .env
@@ -172,7 +174,7 @@ def run(self):
172174 if self .content :
173175 logger .warning (
174176 'Ignoring inline code in Jupyter cell included from "{}"'
175- .format (rel_filename )
177+ .format (rel_filename ), location = location
176178 )
177179 try :
178180 with open (filename ) as f :
@@ -185,6 +187,23 @@ def run(self):
185187 self .assert_has_content ()
186188 content = self .content
187189
190+ # The code fragment is taken from CodeBlock directive almost unchanged:
191+ # https://github.com/sphinx-doc/sphinx/blob/0319faf8f1503453b6ce19020819a8cf44e39f13/sphinx/directives/code.py#L134-L148
192+ emphasize_linespec = self .options .get ('emphasize-lines' )
193+ if emphasize_linespec :
194+ try :
195+ nlines = len (content )
196+ hl_lines = parselinenos (emphasize_linespec , nlines )
197+ if any (i >= nlines for i in hl_lines ):
198+ logger .warning (
199+ 'Line number spec is out of range(1-{}): {}' .format (
200+ nlines , emphasize_linespec ), location = location )
201+ hl_lines = [i + 1 for i in hl_lines if i < nlines ]
202+ except ValueError as err :
203+ return [self .state .document .reporter .warning (err , line = self .lineno )]
204+ else :
205+ hl_lines = []
206+
188207 return [JupyterCellNode (
189208 '' ,
190209 docutils .nodes .literal_block (
@@ -194,6 +213,7 @@ def run(self):
194213 hide_output = ('hide-output' in self .options ),
195214 code_below = ('code-below' in self .options ),
196215 linenos = ('linenos' in self .options ),
216+ emphasize_lines = hl_lines ,
197217 raises = self .options .get ('raises' ),
198218 stderr = ('stderr' in self .options ),
199219 )]
@@ -408,12 +428,18 @@ def apply(self):
408428 linenostart = 1
409429 for node in nodes :
410430 source = node .children [0 ]
431+ nlines = source .rawsource .count ("\n " ) + 1
432+
411433 if linenos_config or continue_linenos or node ["linenos" ]:
412434 source ["linenos" ] = True
413435 if continue_linenos :
414- source [" highlight_args" ] = {'linenostart' : linenostart }
415- linenostart += source . rawsource . count ( " \n " ) + 1
436+ source [' highlight_args' ] = {'linenostart' : linenostart }
437+ linenostart += nlines
416438
439+ hl_lines = node ['emphasize_lines' ]
440+ if hl_lines :
441+ highlight_args = source .setdefault ('highlight_args' , {})
442+ highlight_args ['hl_lines' ] = hl_lines
417443
418444 # Add code cell CSS class
419445 for node in nodes :
0 commit comments