Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions app/containers/codeArea/highlighting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import HighlightJS from 'highlight.js'

export function highlightContent (content, language) {
if (language && HighlightJS.getLanguage(language)) {
try {
return HighlightJS.highlight(content, { language, ignoreIllegals: true }).value
} catch (__) {}
}

return HighlightJS.highlightAuto(content).value
}

export function adaptedLanguage (filename, lang) {
let language = lang || 'Other'

// Adjust the language based on file extensions.
const filenameExtension = filename && filename.split('.').pop().toLowerCase()
switch (filenameExtension) {
case 'c':
case 'h':
if (!lang) language = 'c'
break
case 'leptonrc':
language = 'json'
break
case 'bat':
case 'cmd':
language = 'dos'
break
case 'zshrc':
language = 'bash'
break
case 'sql':
language = 'sql'
break
case 'solidity':
case 'sol':
language = 'solidity'
break
case 'vue':
language = 'xml'
break
default:
// intentionally left blank
}

// Adapt the language name for Highlight.js. For example, 'C#' should be
// expressed as 'cs' to be recognized by Highlight.js.
switch (language) {
case 'Shell': return 'bash'
case 'C': return 'c'
case 'C++': return 'cpp'
case 'C#': return 'cs'
case 'Objective-C': return 'objectivec'
case 'Objective-C++': return 'objectivec'
case 'Visual Basic': return 'vbscript'
case 'Batchfile': return 'dos'
case 'Vue': return 'xml'
default:
}

return language
}
52 changes: 3 additions & 49 deletions app/containers/codeArea/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Markdown from '../../utilities/markdown'
import nb from '../../utilities/jupyterNotebook'
import React, { Component } from 'react'
import electronBridge from '../../utilities/electronBridge'
import { adaptedLanguage, highlightContent } from './highlighting'

import '../../utilities/vendor/prism/prism.scss'
import './jupyterNotebook.scss'
Expand Down Expand Up @@ -41,7 +42,7 @@ export default class CodeArea extends Component {

createHighlightedCodeBlock (content, language, kTabLength) {
let lineNumber = 0
const highlightedContent = HighlightJS.highlightAuto(content, [language]).value
const highlightedContent = highlightContent(content, language)

/*
Highlight.js wraps comment blocks inside <span class='hljs-comment'></span>.
Expand All @@ -68,55 +69,8 @@ export default class CodeArea extends Component {
return `<pre><code><table class='code-table' style='tab-size: ${kTabLength};'>${contentTable}</table></code></pre>`
}

// Find the best language for code highlighting by best effort.
adaptedLanguage (filename, lang) {
let language = lang || 'Other'

// Adjust the language based on file extensions.
const filenameExtension = filename.split('.').pop().toLowerCase()
switch (filenameExtension) {
case 'leptonrc':
language = 'json'
break
case 'bat':
case 'cmd':
language = 'dos'
break
case 'zshrc':
language = 'bash'
break
case 'sql':
language = 'sql'
break
case 'solidity':
case 'sol':
language = 'solidity'
break
case 'vue':
language = 'xml'
break
default:
// intentionally left blank
}

// Adapt the language name for Highlight.js. For example, 'C#' should be
// expressed as 'cs' to be recognized by Highlight.js.
switch (language) {
case 'Shell': return 'bash'
case 'C#': return 'cs'
case 'Objective-C': return 'objectivec'
case 'Objective-C++': return 'objectivec'
case 'Visual Basic': return 'vbscript'
case 'Batchfile': return 'dos'
case 'Vue': return 'xml'
default:
}

return language
}

renderCodeArea (filename, content, lang, kTabLength) {
const language = this.adaptedLanguage(filename, lang)
const language = adaptedLanguage(filename, lang)
let htmlContent = ''
switch (language) {
case 'Jupyter Notebook':
Expand Down
56 changes: 56 additions & 0 deletions tests/containers/codeArea.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest'

import {
adaptedLanguage,
highlightContent
} from '../../app/containers/codeArea/highlighting'

const cSource = '#include<stdio.h>\n\nint main(void) {\n printf("Hello World\\n");\n return 0;\n}'

describe('CodeArea syntax highlighting', () => {
it('normalizes GitHub language names for Highlight.js', () => {
expect(adaptedLanguage('main.c', 'C')).toBe('c')
expect(adaptedLanguage('main.cpp', 'C++')).toBe('cpp')
expect(adaptedLanguage('Program.cs', 'C#')).toBe('cs')
expect(adaptedLanguage('App.m', 'Objective-C')).toBe('objectivec')
expect(adaptedLanguage('App.mm', 'Objective-C++')).toBe('objectivec')
expect(adaptedLanguage('script.bat', 'Batchfile')).toBe('dos')
expect(adaptedLanguage('App.vue', 'Vue')).toBe('xml')
expect(adaptedLanguage('shell.sh', 'Shell')).toBe('bash')
expect(adaptedLanguage('form.vb', 'Visual Basic')).toBe('vbscript')
})

it('falls back to C highlighting for C source and header file extensions', () => {
expect(adaptedLanguage('main.c')).toBe('c')
expect(adaptedLanguage('main.h')).toBe('c')
})

it('uses extension overrides for files without GitHub language metadata', () => {
expect(adaptedLanguage('.leptonrc')).toBe('json')
expect(adaptedLanguage('setup.CMD')).toBe('dos')
expect(adaptedLanguage('.zshrc')).toBe('bash')
expect(adaptedLanguage('query.SQL')).toBe('sql')
expect(adaptedLanguage('Contract.sol')).toBe('solidity')
expect(adaptedLanguage('Contract.solidity')).toBe('solidity')
expect(adaptedLanguage('Component.VUE')).toBe('xml')
})

it('does not let ambiguous C header fallback override provided metadata', () => {
expect(adaptedLanguage('types.h', 'C++')).toBe('cpp')
expect(adaptedLanguage('ObjCBridge.h', 'Objective-C')).toBe('objectivec')
})

it('renders C source with Highlight.js spans instead of escaped plain text', () => {
const html = highlightContent(cSource, adaptedLanguage('main.c', 'C'))

expect(html).toContain('hljs-keyword')
expect(html).toContain('hljs-string')
expect(html).toContain('&lt;stdio.h&gt;')
})

it('falls back to automatic highlighting for unknown language names', () => {
const html = highlightContent('function answer() { return 42 }', 'Other')

expect(html).toContain('hljs-keyword')
})
})
Loading