Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Line highlighting within code block #2469 #2682

Merged
merged 14 commits into from
Dec 24, 2018
Merged
Show file tree
Hide file tree
Changes from 13 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
113 changes: 113 additions & 0 deletions browser/components/CodeEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class CodeEditor extends React.Component {
trailing: true
})
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
Expand Down Expand Up @@ -214,6 +215,7 @@ export default class CodeEditor extends React.Component {
this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
linesHighlighted: this.props.linesHighlighted,
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
theme: this.props.theme,
Expand All @@ -240,6 +242,7 @@ export default class CodeEditor extends React.Component {
this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
this.editor.on('gutterClick', this.highlightHandler)
this.editor.on('paste', this.pasteHandler)
this.editor.on('contextmenu', this.contextMenuHandler)
eventEmitter.on('top:search', this.searchHandler)
Expand Down Expand Up @@ -316,6 +319,8 @@ export default class CodeEditor extends React.Component {
this.setState({
clientWidth: this.refs.root.clientWidth
})

this.initialHighlighting()
}

expandSnippet (line, cursor, cm, snippets) {
Expand Down Expand Up @@ -506,12 +511,96 @@ export default class CodeEditor extends React.Component {

handleChange (editor, changeObject) {
spellcheck.handleChange(editor, changeObject)

this.updateHighlight(editor, changeObject)

this.value = editor.getValue()
if (this.props.onChange) {
this.props.onChange(editor)
}
}

incrementLines (start, linesAdded, linesRemoved, editor) {
let highlightedLines = editor.options.linesHighlighted

const totalHighlightedLines = highlightedLines.length

let offset = linesAdded - linesRemoved

// Store new items to be added as we're changing the lines
let newLines = []

let i = totalHighlightedLines

while (i--) {
const lineNumber = highlightedLines[i]

// Interval that will need to be updated
// Between start and (start + offset) remove highlight
if (lineNumber >= start) {
highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)

// Lines that need to be relocated
if (lineNumber >= (start + linesRemoved)) {
newLines.push(lineNumber + offset)
}
}
}

// Adding relocated lines
highlightedLines.push(...newLines)

if (this.props.onChange) {
this.props.onChange(editor)
}
}

handleHighlight (editor, changeObject) {
const lines = editor.options.linesHighlighted

if (!lines.includes(changeObject)) {
lines.push(changeObject)
editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
} else {
lines.splice(lines.indexOf(changeObject), 1)
editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
}
if (this.props.onChange) {
this.props.onChange(editor)
}
}

updateHighlight (editor, changeObject) {
const linesAdded = changeObject.text.length - 1
const linesRemoved = changeObject.removed.length - 1

// If no lines added or removed return
if (linesAdded === 0 && linesRemoved === 0) {
return
}

let start = changeObject.from.line

switch (changeObject.origin) {
case '+insert", "undo':
start += 1
break

case 'paste':
case '+delete':
case '+input':
if (changeObject.to.ch !== 0 || changeObject.from.ch !== 0) {
start += 1
}
break

default:
return
}

this.incrementLines(start, linesAdded, linesRemoved, editor)
}

moveCursorTo (row, col) {}

scrollToLine (event, num) {
Expand All @@ -536,6 +625,7 @@ export default class CodeEditor extends React.Component {
this.value = this.props.value
this.editor.setValue(this.props.value)
this.editor.clearHistory()
this.restartHighlighting()
this.editor.on('change', this.changeHandler)
this.editor.refresh()
}
Expand Down Expand Up @@ -683,6 +773,29 @@ export default class CodeEditor extends React.Component {
})
}

initialHighlighting () {
if (this.editor.options.linesHighlighted == null) {
return
}

const totalHighlightedLines = this.editor.options.linesHighlighted.length
const totalAvailableLines = this.editor.lineCount()

for (let i = 0; i < totalHighlightedLines; i++) {
const lineNumber = this.editor.options.linesHighlighted[i]
if (lineNumber > totalAvailableLines) {
// make sure that we skip the invalid lines althrough this case should not be happened.
continue
}
this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background')
}
}

restartHighlighting () {
this.editor.options.linesHighlighted = this.props.linesHighlighted
this.initialHighlighting()
}

mapImageResponse (response, pastedTxt) {
return new Promise((resolve, reject) => {
try {
Expand Down
5 changes: 3 additions & 2 deletions browser/components/MarkdownEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ class MarkdownEditor extends React.Component {
}

render () {
const {className, value, config, storageKey, noteKey} = this.props
const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props

let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
Expand Down Expand Up @@ -275,7 +275,8 @@ class MarkdownEditor extends React.Component {
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleChange(e)}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange.bind(this)(e)}
duartefrazao marked this conversation as resolved.
Show resolved Hide resolved
onBlur={(e) => this.handleBlur(e)}
spellCheck={config.editor.spellcheck}
/>
Expand Down
9 changes: 5 additions & 4 deletions browser/components/MarkdownSplitEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class MarkdownSplitEditor extends React.Component {
this.refs.code.setValue(value)
}

handleOnChange () {
handleOnChange (e) {
this.value = this.refs.code.value
this.props.onChange()
this.props.onChange(e)
}

handleScroll (e) {
Expand Down Expand Up @@ -136,7 +136,7 @@ class MarkdownSplitEditor extends React.Component {
}

render () {
const {config, value, storageKey, noteKey} = this.props
const {config, value, storageKey, noteKey, linesHighlighted} = this.props
const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
Expand Down Expand Up @@ -169,7 +169,8 @@ class MarkdownSplitEditor extends React.Component {
enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey}
noteKey={noteKey}
onChange={this.handleOnChange.bind(this)}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleOnChange.bind(this)(e)}
duartefrazao marked this conversation as resolved.
Show resolved Hide resolved
onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck}
/>
Expand Down
6 changes: 4 additions & 2 deletions browser/lib/newNote.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
folder: folder,
title: '',
tags,
content: ''
content: '',
linesHighlighted: []
})
.then(note => {
const noteHash = note.key
Expand Down Expand Up @@ -56,7 +57,8 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
content: ''
content: '',
linesHighlighted: []
}
]
})
Expand Down
8 changes: 6 additions & 2 deletions browser/main/Detail/MarkdownNoteDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ class MarkdownNoteDetail extends React.Component {
isMovingNote: false,
note: Object.assign({
title: '',
content: ''
content: '',
linesHighlighted: []
}, props.note),
isLockButtonShown: false,
isLocked: false,
editorType: props.config.editor.type
}

this.dispatchTimer = null

this.toggleLockButton = this.handleToggleLockButton.bind(this)
Expand All @@ -71,7 +73,7 @@ class MarkdownNoteDetail extends React.Component {
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow()
this.setState({
note: Object.assign({}, nextProps.note)
note: Object.assign({linesHighlighted: []}, nextProps.note)
}, () => {
this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset()
Expand Down Expand Up @@ -361,6 +363,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content}
storageKey={note.storage}
noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
Expand All @@ -371,6 +374,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content}
storageKey={note.storage}
noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
Expand Down
14 changes: 9 additions & 5 deletions browser/main/Detail/SnippetNoteDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class SnippetNoteDetail extends React.Component {
note: Object.assign({
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
})
}

Expand Down Expand Up @@ -76,8 +76,9 @@ class SnippetNoteDetail extends React.Component {
const nextNote = Object.assign({
description: ''
}, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
})

this.setState({
snippetIndex: 0,
note: nextNote
Expand Down Expand Up @@ -410,6 +411,8 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
snippets[index].linesHighlighted = e.options.linesHighlighted

this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: state.note
Expand Down Expand Up @@ -602,7 +605,8 @@ class SnippetNoteDetail extends React.Component {
note.snippets = note.snippets.concat([{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
content: ''
content: '',
linesHighlighted: []
}])
const snippetIndex = note.snippets.length - 1

Expand Down Expand Up @@ -685,10 +689,8 @@ class SnippetNoteDetail extends React.Component {

const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index

let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')

return <div styleName='tabView'
key={index}
style={{zIndex: isActive ? 5 : 4}}
Expand All @@ -697,6 +699,7 @@ class SnippetNoteDetail extends React.Component {
? <MarkdownEditor styleName='tabView-content'
value={snippet.content}
config={config}
linesHighlighted={snippet.linesHighlighted}
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
Expand All @@ -705,6 +708,7 @@ class SnippetNoteDetail extends React.Component {
: <CodeEditor styleName='tabView-content'
mode={snippet.mode}
value={snippet.content}
linesHighlighted={snippet.linesHighlighted}
theme={config.editor.theme}
fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
Expand Down
6 changes: 4 additions & 2 deletions browser/main/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,14 @@ class Main extends React.Component {
{
name: 'example.html',
mode: 'html',
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>"
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
linesHighlighted: []
},
{
name: 'example.js',
mode: 'javascript',
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)"
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)",
linesHighlighted: []
}
]
})
Expand Down
3 changes: 2 additions & 1 deletion browser/main/NoteList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,8 @@ class NoteList extends React.Component {
type: firstNote.type,
folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content
content: firstNote.content,
linesHighlighted: firstNote.linesHighlighted
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
Expand Down
4 changes: 3 additions & 1 deletion browser/main/lib/dataApi/createNote.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ function validateInput (input) {
switch (input.type) {
case 'MARKDOWN_NOTE':
if (!_.isString(input.content)) input.content = ''
if (!_.isArray(input.linesHighlighted)) input.linesHighlighted = []
break
case 'SNIPPET_NOTE':
if (!_.isString(input.description)) input.description = ''
if (!_.isArray(input.snippets)) {
input.snippets = [{
name: '',
mode: 'text',
content: ''
content: '',
linesHighlighted: []
}]
}
break
Expand Down
3 changes: 2 additions & 1 deletion browser/main/lib/dataApi/createSnippet.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ function createSnippet (snippetFile) {
id: crypto.randomBytes(16).toString('hex'),
name: 'Unnamed snippet',
prefix: [],
content: ''
content: '',
linesHighlighted: []
}
fetchSnippet(null, snippetFile).then((snippets) => {
snippets.push(newSnippet)
Expand Down
Loading