@@ -4,6 +4,7 @@ var Cookies = require('js-cookie');
44
55var Codesearch = require ( 'codesearch/codesearch.js' ) . Codesearch ;
66var RepoSelector = require ( 'codesearch/repo_selector.js' ) ;
7+ var highlight = require ( 'codesearch/highlight.js' ) ;
78
89var KeyCodes = {
910 SLASH_OR_QUESTION_MARK : 191
@@ -19,6 +20,53 @@ function init(initData) {
1920var h = new html . HTMLFactory ( ) ;
2021var last_url_update = 0 ;
2122
23+ var PRISM_THEMES_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/prism-themes/1.9.0/' ;
24+
25+ // NOTE: keep in sync with the early-load script in layout.html (mode + css only)
26+ var syntaxThemes = [
27+ { key :'dracula' , label :'Dracula' , mode :'dark' , css :'prism-dracula.min.css' } ,
28+ { key :'one-dark' , label :'One Dark' , mode :'dark' , css :'prism-one-dark.min.css' } ,
29+ { key :'nord' , label :'Nord' , mode :'dark' , css :'prism-nord.min.css' } ,
30+ { key :'solarized-dark' , label :'Solarized Dark' , mode :'dark' , css :'prism-solarized-dark-atom.min.css' } ,
31+ { key :'material-dark' , label :'Material Dark' , mode :'dark' , css :'prism-material-dark.min.css' } ,
32+ { key :'gruvbox-dark' , label :'Gruvbox Dark' , mode :'dark' , css :'prism-gruvbox-dark.min.css' } ,
33+ { key :'vsc-dark' , label :'VS Code Dark' , mode :'dark' , css :'prism-vsc-dark-plus.min.css' } ,
34+ { key :'one-light' , label :'One Light' , mode :'light' , css :'prism-one-light.min.css' } ,
35+ { key :'material-light' , label :'Material Light' , mode :'light' , css :'prism-material-light.min.css' } ,
36+ { key :'gruvbox-light' , label :'Gruvbox Light' , mode :'light' , css :'prism-gruvbox-light.min.css' }
37+ ] ;
38+
39+ var syntaxThemeMap = { } ;
40+ syntaxThemes . forEach ( function ( t ) { syntaxThemeMap [ t . key ] = t ; } ) ;
41+
42+ function applyPageMode ( mode ) {
43+ if ( mode === 'light' || mode === 'dark' ) {
44+ document . documentElement . setAttribute ( 'data-theme' , mode ) ;
45+ } else {
46+ document . documentElement . removeAttribute ( 'data-theme' ) ;
47+ }
48+ }
49+
50+ function applySyntaxTheme ( themeName ) {
51+ var existing = document . getElementById ( 'syntax-theme-css' ) ;
52+ var theme = syntaxThemeMap [ themeName ] ;
53+ if ( theme ) {
54+ applyPageMode ( theme . mode ) ;
55+ if ( existing ) {
56+ existing . href = PRISM_THEMES_CDN + theme . css ;
57+ } else {
58+ var link = document . createElement ( 'link' ) ;
59+ link . id = 'syntax-theme-css' ;
60+ link . rel = 'stylesheet' ;
61+ link . href = PRISM_THEMES_CDN + theme . css ;
62+ document . head . appendChild ( link ) ;
63+ }
64+ } else {
65+ applyPageMode ( 'auto' ) ;
66+ if ( existing ) existing . remove ( ) ;
67+ }
68+ }
69+
2270function vercmp ( a , b ) {
2371 var re = / ^ ( [ 0 - 9 ] * ) ( [ ^ 0 - 9 ] * ) ( .* ) $ / ;
2472 var abits , bbits ;
@@ -173,20 +221,25 @@ var MatchView = Backbone.View.extend({
173221 var lno = this . model . get ( 'lno' ) ;
174222 var ctxBefore = this . model . get ( 'context_before' ) , clip_before = this . model . get ( 'clip_before' ) ;
175223 var ctxAfter = this . model . get ( 'context_after' ) , clip_after = this . model . get ( 'clip_after' ) ;
224+ var language = this . options . language ;
176225
177226 var lines_to_display_before = Math . max ( 0 , ctxBefore . length - ( clip_before || 0 ) ) ;
178227 for ( i = 0 ; i < lines_to_display_before ; i ++ ) {
228+ var ctxTextBefore = this . model . get ( 'context_before' ) [ i ] ;
229+ var ctxNodesBefore = highlight . highlightContext ( ctxTextBefore , language ) ;
179230 ctx_before . unshift (
180231 this . _renderLno ( lno - i - 1 , false ) ,
181- h . span ( [ this . model . get ( 'context_before' ) [ i ] ] ) ,
232+ ctxNodesBefore ? h . span ( ctxNodesBefore ) : h . span ( [ ctxTextBefore ] ) ,
182233 h . span ( { } , [ ] )
183234 ) ;
184235 }
185236 var lines_to_display_after = Math . max ( 0 , ctxAfter . length - ( clip_after || 0 ) ) ;
186237 for ( i = 0 ; i < lines_to_display_after ; i ++ ) {
238+ var ctxTextAfter = this . model . get ( 'context_after' ) [ i ] ;
239+ var ctxNodesAfter = highlight . highlightContext ( ctxTextAfter , language ) ;
187240 ctx_after . push (
188241 this . _renderLno ( lno + i + 1 , false ) ,
189- h . span ( [ this . model . get ( 'context_after' ) [ i ] ] ) ,
242+ ctxNodesAfter ? h . span ( ctxNodesAfter ) : h . span ( [ ctxTextAfter ] ) ,
190243 h . span ( { } , [ ] )
191244 ) ;
192245 }
@@ -196,6 +249,14 @@ var MatchView = Backbone.View.extend({
196249 line . substring ( bounds [ 0 ] , bounds [ 1 ] ) ,
197250 line . substring ( bounds [ 1 ] ) ] ;
198251
252+ var highlightedNodes = highlight . highlightLine ( line , language , bounds ) ;
253+ var matchLineContent ;
254+ if ( highlightedNodes ) {
255+ matchLineContent = h . span ( { cls : 'matchline' } , highlightedNodes ) ;
256+ } else {
257+ matchLineContent = h . span ( { cls : 'matchline' } , [ pieces [ 0 ] , h . span ( { cls : 'matchstr' } , [ pieces [ 1 ] ] ) , pieces [ 2 ] ] ) ;
258+ }
259+
199260 var classes = [ 'match' ] ;
200261 if ( clip_before !== undefined ) classes . push ( 'clip-before' ) ;
201262 if ( clip_after !== undefined ) classes . push ( 'clip-after' ) ;
@@ -215,7 +276,7 @@ var MatchView = Backbone.View.extend({
215276 ctx_before ,
216277 [
217278 this . _renderLno ( lno , true ) ,
218- h . span ( { cls : 'matchline' } , [ pieces [ 0 ] , h . span ( { cls : 'matchstr' } , [ pieces [ 1 ] ] ) , pieces [ 2 ] ] ) ,
279+ matchLineContent ,
219280 h . span ( { cls : 'matchlinks' } , links )
220281 ] ,
221282 ctx_after
@@ -553,14 +614,30 @@ var FileGroupView = Backbone.View.extend({
553614 render : function ( ) {
554615 var matches = this . model . matches ;
555616 var el = this . $el ;
617+ var self = this ;
556618 el . empty ( ) ;
557619 el . append ( this . render_header ( this . model . path_info . tree , this . model . path_info . version , this . model . path_info . path ) ) ;
620+ var language = highlight . detectLanguage ( this . model . path_info . path ) ;
558621 matches . forEach ( function ( match ) {
559622 el . append (
560- new MatchView ( { model :match } ) . render ( ) . el
623+ new MatchView ( { model :match , language : language } ) . render ( ) . el
561624 ) ;
562625 } ) ;
563626 el . addClass ( 'file-group' ) ;
627+
628+ if ( language && typeof Prism !== 'undefined' && ! Prism . languages [ language ] &&
629+ Prism . plugins && Prism . plugins . autoloader && ! self . _loadingLanguage ) {
630+ self . _loadingLanguage = true ;
631+ Prism . plugins . autoloader . loadLanguages ( [ language ] , function ( ) {
632+ self . _loadingLanguage = false ;
633+ if ( Prism . languages [ language ] ) {
634+ self . render ( ) ;
635+ }
636+ } , function ( ) {
637+ self . _loadingLanguage = false ;
638+ } ) ;
639+ }
640+
564641 return this ;
565642 }
566643} ) ;
@@ -787,6 +864,13 @@ var CodesearchUI = function() {
787864 CodesearchUI . inputs_case = $ ( 'input[name=fold_case]' ) ;
788865 CodesearchUI . input_regex = $ ( 'input[name=regex]' ) ;
789866 CodesearchUI . input_context = $ ( 'input[name=context]' ) ;
867+ CodesearchUI . input_syntax_theme = $ ( '#syntax-theme' ) ;
868+ if ( CodesearchUI . input_syntax_theme . length ) {
869+ CodesearchUI . input_syntax_theme . append ( $ ( '<option>' ) . val ( 'default' ) . text ( 'Default' ) ) ;
870+ syntaxThemes . forEach ( function ( t ) {
871+ CodesearchUI . input_syntax_theme . append ( $ ( '<option>' ) . val ( t . key ) . text ( t . label ) ) ;
872+ } ) ;
873+ }
790874
791875 if ( CodesearchUI . inputs_case . filter ( ':checked' ) . length == 0 ) {
792876 CodesearchUI . inputs_case . filter ( '[value=auto]' ) . attr ( 'checked' , true ) ;
@@ -813,6 +897,12 @@ var CodesearchUI = function() {
813897 CodesearchUI . set_pref ( 'context' , CodesearchUI . input_context . prop ( 'checked' ) ) ;
814898 } ) ;
815899
900+ CodesearchUI . input_syntax_theme . change ( function ( ) {
901+ var theme = CodesearchUI . input_syntax_theme . val ( ) ;
902+ applySyntaxTheme ( theme ) ;
903+ CodesearchUI . set_pref ( 'syntaxTheme' , theme ) ;
904+ } ) ;
905+
816906 CodesearchUI . toggle_context ( ) ;
817907
818908 // Defer heavy repo dropdown initialization so the page can paint first.
@@ -912,6 +1002,13 @@ var CodesearchUI = function() {
9121002 if ( parms [ 'repo[]' ] )
9131003 repos = repos . concat ( parms [ 'repo[]' ] ) ;
9141004 RepoSelector . updateSelected ( repos ) ;
1005+
1006+ // Sync theme dropdown from saved prefs (the early-load script in
1007+ // layout.html already applies the visual theme; this syncs the control)
1008+ var prefs = Cookies . getJSON ( 'prefs' ) ;
1009+ if ( prefs && prefs [ 'syntaxTheme' ] !== undefined && CodesearchUI . input_syntax_theme . length ) {
1010+ CodesearchUI . input_syntax_theme . val ( prefs [ 'syntaxTheme' ] ) ;
1011+ }
9151012 } ,
9161013 init_controls_from_prefs : function ( ) {
9171014 var prefs = Cookies . getJSON ( 'prefs' ) ;
@@ -929,6 +1026,10 @@ var CodesearchUI = function() {
9291026 if ( prefs [ 'context' ] !== undefined ) {
9301027 CodesearchUI . input_context . prop ( 'checked' , prefs [ 'context' ] ) ;
9311028 }
1029+ if ( prefs [ 'syntaxTheme' ] !== undefined && CodesearchUI . input_syntax_theme . length ) {
1030+ CodesearchUI . input_syntax_theme . val ( prefs [ 'syntaxTheme' ] ) ;
1031+ applySyntaxTheme ( prefs [ 'syntaxTheme' ] ) ;
1032+ }
9321033 } ,
9331034 set_pref : function ( key , value ) {
9341035 // Load from the cookie again every time in case some other pref has been
0 commit comments