From b5b56f7af1ffe0ed83902bffd0fda9d146c4b350 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Hung Date: Thu, 21 Mar 2019 05:26:53 +1300 Subject: [PATCH] Refactor code editor by moving the expand snippet out to a separate file (#2864) * refactored CodeEditor by moving Snippet out * fixed typo --- browser/components/CodeEditor.js | 174 ++---------------- browser/lib/CMLanguageList.js | 78 ++++++++ browser/lib/SnippetManager.js | 91 +++++++++ .../modals/PreferencesModal/SnippetEditor.js | 5 +- 4 files changed, 188 insertions(+), 160 deletions(-) create mode 100644 browser/lib/CMLanguageList.js create mode 100644 browser/lib/SnippetManager.js diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 8b06cd72..0ddfd5c9 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -14,18 +14,14 @@ import { import TextEditorInterface from 'browser/lib/TextEditorInterface' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' -import crypto from 'crypto' -import consts from 'browser/lib/consts' import styles from '../components/CodeEditor.styl' -import fs from 'fs' const { ipcRenderer, remote, clipboard } = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' const spellcheck = require('browser/lib/spellcheck') const buildEditorContextMenu = require('browser/lib/contextMenuBuilder') import TurndownService from 'turndown' -import { - gfm -} from 'turndown-plugin-gfm' +import {languageMaps} from '../lib/CMLanguageList' +import snippetManager from '../lib/SnippetManager' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -38,85 +34,6 @@ function translateHotkey (hotkey) { return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl') } -const languageMaps = { - brainfuck: 'Brainfuck', - cpp: 'C++', - cs: 'C#', - clojure: 'Clojure', - 'clojure-repl': 'ClojureScript', - cmake: 'CMake', - coffeescript: 'CoffeeScript', - crystal: 'Crystal', - css: 'CSS', - d: 'D', - dart: 'Dart', - delphi: 'Pascal', - diff: 'Diff', - django: 'Django', - dockerfile: 'Dockerfile', - ebnf: 'EBNF', - elm: 'Elm', - erlang: 'Erlang', - 'erlang-repl': 'Erlang', - fortran: 'Fortran', - fsharp: 'F#', - gherkin: 'Gherkin', - go: 'Go', - groovy: 'Groovy', - haml: 'HAML', - haskell: 'Haskell', - haxe: 'Haxe', - http: 'HTTP', - ini: 'toml', - java: 'Java', - javascript: 'JavaScript', - json: 'JSON', - julia: 'Julia', - kotlin: 'Kotlin', - less: 'LESS', - livescript: 'LiveScript', - lua: 'Lua', - markdown: 'Markdown', - mathematica: 'Mathematica', - nginx: 'Nginx', - nsis: 'NSIS', - objectivec: 'Objective-C', - ocaml: 'Ocaml', - perl: 'Perl', - php: 'PHP', - powershell: 'PowerShell', - properties: 'Properties files', - protobuf: 'ProtoBuf', - python: 'Python', - puppet: 'Puppet', - q: 'Q', - r: 'R', - ruby: 'Ruby', - rust: 'Rust', - sas: 'SAS', - scala: 'Scala', - scheme: 'Scheme', - scss: 'SCSS', - shell: 'Shell', - smalltalk: 'Smalltalk', - sml: 'SML', - sql: 'SQL', - stylus: 'Stylus', - swift: 'Swift', - tcl: 'Tcl', - tex: 'LaTex', - typescript: 'TypeScript', - twig: 'Twig', - vbnet: 'VB.NET', - vbscript: 'VBScript', - verilog: 'Verilog', - vhdl: 'VHDL', - xml: 'HTML', - xquery: 'XQuery', - yaml: 'YAML', - elixir: 'Elixir' -} - export default class CodeEditor extends React.Component { constructor (props) { super(props) @@ -228,7 +145,8 @@ export default class CodeEditor extends React.Component { updateDefaultKeyMap () { const { hotkey } = this.props - const expandSnippet = this.expandSnippet.bind(this) + const self = this + const expandSnippet = snippetManager.expandSnippet this.defaultKeyMap = CodeMirror.normalizeKeyMap({ Tab: function (cm) { @@ -252,10 +170,12 @@ export default class CodeEditor extends React.Component { cursor.ch > 1 ) { // text expansion on tab key if the char before is alphabet - const snippets = JSON.parse( - fs.readFileSync(consts.SNIPPET_FILE, 'utf8') + const wordBeforeCursor = self.getWordBeforeCursor( + line, + cursor.line, + cursor.ch ) - if (expandSnippet(line, cursor, cm, snippets) === false) { + if (expandSnippet(wordBeforeCursor, cursor, cm) === false) { if (tabs) { cm.execCommand('insertTab') } else { @@ -310,22 +230,7 @@ export default class CodeEditor extends React.Component { const { rulers, enableRulers } = this.props eventEmitter.on('line:jump', this.scrollToLineHandeler) - const defaultSnippet = [ - { - id: crypto.randomBytes(16).toString('hex'), - name: 'Dummy text', - prefix: ['lorem', 'ipsum'], - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - } - ] - if (!fs.existsSync(consts.SNIPPET_FILE)) { - fs.writeFileSync( - consts.SNIPPET_FILE, - JSON.stringify(defaultSnippet, null, 4), - 'utf8' - ) - } - + snippetManager.init() this.updateDefaultKeyMap() this.value = this.props.value @@ -520,61 +425,12 @@ export default class CodeEditor extends React.Component { this.initialHighlighting() } - expandSnippet (line, cursor, cm, snippets) { - const wordBeforeCursor = this.getWordBeforeCursor( - line, - cursor.line, - cursor.ch - ) - const templateCursorString = ':{}' - for (let i = 0; i < snippets.length; i++) { - if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) { - if (snippets[i].content.indexOf(templateCursorString) !== -1) { - const snippetLines = snippets[i].content.split('\n') - let cursorLineNumber = 0 - let cursorLinePosition = 0 - - let cursorIndex - for (let j = 0; j < snippetLines.length; j++) { - cursorIndex = snippetLines[j].indexOf(templateCursorString) - - if (cursorIndex !== -1) { - cursorLineNumber = j - cursorLinePosition = cursorIndex - - break - } - } - - cm.replaceRange( - snippets[i].content.replace(templateCursorString, ''), - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) - cm.setCursor({ - line: cursor.line + cursorLineNumber, - ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length - }) - } else { - cm.replaceRange( - snippets[i].content, - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) - } - return true - } - } - - return false - } - getWordBeforeCursor (line, lineNumber, cursorPosition) { let wordBeforeCursor = '' const originCursorPosition = cursorPosition const emptyChars = /\t|\s|\r|\n/ - // to prevent the word to expand is long that will crash the whole app + // to prevent the word is long that will crash the whole app // the safeStop is there to stop user to expand words that longer than 20 chars const safeStop = 20 @@ -584,7 +440,7 @@ export default class CodeEditor extends React.Component { if (!emptyChars.test(currentChar)) { wordBeforeCursor = currentChar + wordBeforeCursor } else if (wordBeforeCursor.length >= safeStop) { - throw new Error('Your snippet trigger is too long !') + throw new Error('Stopped after 20 loops for safety reason !') } else { break } @@ -747,14 +603,14 @@ export default class CodeEditor extends React.Component { } incrementLines (start, linesAdded, linesRemoved, editor) { - let highlightedLines = editor.options.linesHighlighted + const highlightedLines = editor.options.linesHighlighted const totalHighlightedLines = highlightedLines.length - let offset = linesAdded - linesRemoved + const offset = linesAdded - linesRemoved // Store new items to be added as we're changing the lines - let newLines = [] + const newLines = [] let i = totalHighlightedLines diff --git a/browser/lib/CMLanguageList.js b/browser/lib/CMLanguageList.js new file mode 100644 index 00000000..0a8652a6 --- /dev/null +++ b/browser/lib/CMLanguageList.js @@ -0,0 +1,78 @@ +export const languageMaps = { + brainfuck: 'Brainfuck', + cpp: 'C++', + cs: 'C#', + clojure: 'Clojure', + 'clojure-repl': 'ClojureScript', + cmake: 'CMake', + coffeescript: 'CoffeeScript', + crystal: 'Crystal', + css: 'CSS', + d: 'D', + dart: 'Dart', + delphi: 'Pascal', + diff: 'Diff', + django: 'Django', + dockerfile: 'Dockerfile', + ebnf: 'EBNF', + elm: 'Elm', + erlang: 'Erlang', + 'erlang-repl': 'Erlang', + fortran: 'Fortran', + fsharp: 'F#', + gherkin: 'Gherkin', + go: 'Go', + groovy: 'Groovy', + haml: 'HAML', + haskell: 'Haskell', + haxe: 'Haxe', + http: 'HTTP', + ini: 'toml', + java: 'Java', + javascript: 'JavaScript', + json: 'JSON', + julia: 'Julia', + kotlin: 'Kotlin', + less: 'LESS', + livescript: 'LiveScript', + lua: 'Lua', + markdown: 'Markdown', + mathematica: 'Mathematica', + nginx: 'Nginx', + nsis: 'NSIS', + objectivec: 'Objective-C', + ocaml: 'Ocaml', + perl: 'Perl', + php: 'PHP', + powershell: 'PowerShell', + properties: 'Properties files', + protobuf: 'ProtoBuf', + python: 'Python', + puppet: 'Puppet', + q: 'Q', + r: 'R', + ruby: 'Ruby', + rust: 'Rust', + sas: 'SAS', + scala: 'Scala', + scheme: 'Scheme', + scss: 'SCSS', + shell: 'Shell', + smalltalk: 'Smalltalk', + sml: 'SML', + sql: 'SQL', + stylus: 'Stylus', + swift: 'Swift', + tcl: 'Tcl', + tex: 'LaTex', + typescript: 'TypeScript', + twig: 'Twig', + vbnet: 'VB.NET', + vbscript: 'VBScript', + verilog: 'Verilog', + vhdl: 'VHDL', + xml: 'HTML', + xquery: 'XQuery', + yaml: 'YAML', + elixir: 'Elixir' +} diff --git a/browser/lib/SnippetManager.js b/browser/lib/SnippetManager.js new file mode 100644 index 00000000..2a2ff856 --- /dev/null +++ b/browser/lib/SnippetManager.js @@ -0,0 +1,91 @@ +import crypto from 'crypto' +import fs from 'fs' +import consts from './consts' + +class SnippetManager { + constructor () { + this.defaultSnippet = [ + { + id: crypto.randomBytes(16).toString('hex'), + name: 'Dummy text', + prefix: ['lorem', 'ipsum'], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + } + ] + this.snippets = [] + this.expandSnippet = this.expandSnippet.bind(this) + this.init = this.init.bind(this) + this.assignSnippet = this.assignSnippet.bind(this) + } + + init () { + if (fs.existsSync(consts.SNIPPET_FILE)) { + try { + this.snippets = JSON.parse( + fs.readFileSync(consts.SNIPPET_FILE, { encoding: 'UTF-8' }) + ) + } catch (error) { + console.log('Error while parsing snippet file') + } + return + } + fs.writeFileSync( + consts.SNIPPET_FILE, + JSON.stringify(this.defaultSnippet, null, 4), + 'utf8' + ) + this.snippets = this.defaultSnippet + } + + assignSnippets (snippets) { + this.snippets = snippets + } + + expandSnippet (wordBeforeCursor, cursor, cm) { + const templateCursorString = ':{}' + for (let i = 0; i < this.snippets.length; i++) { + if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) { + continue + } + if (this.snippets[i].content.indexOf(templateCursorString) !== -1) { + const snippetLines = this.snippets[i].content.split('\n') + let cursorLineNumber = 0 + let cursorLinePosition = 0 + + let cursorIndex + for (let j = 0; j < snippetLines.length; j++) { + cursorIndex = snippetLines[j].indexOf(templateCursorString) + + if (cursorIndex !== -1) { + cursorLineNumber = j + cursorLinePosition = cursorIndex + + break + } + } + + cm.replaceRange( + this.snippets[i].content.replace(templateCursorString, ''), + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + cm.setCursor({ + line: cursor.line + cursorLineNumber, + ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length + }) + } else { + cm.replaceRange( + this.snippets[i].content, + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) + } + return true + } + + return false + } +} + +const manager = new SnippetManager() +export default manager diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 071f265f..e95afdcf 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -4,6 +4,7 @@ import _ from 'lodash' import styles from './SnippetTab.styl' import CSSModules from 'browser/lib/CSSModules' import dataApi from 'browser/main/lib/dataApi' +import snippetManager from '../../../lib/SnippetManager' const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const buildCMRulers = (rulers, enableRulers) => @@ -64,7 +65,9 @@ class SnippetEditor extends React.Component { } saveSnippet () { - dataApi.updateSnippet(this.snippet).catch((err) => { throw err }) + dataApi.updateSnippet(this.snippet) + .then(snippets => snippetManager.assignSnippets(snippets)) + .catch((err) => { throw err }) } render () {