Simple Extensible Code Editor – Copenhagen

Category: Javascript , Recommended | April 17, 2021
Author: acode
Views Total: 32 views
Official Page: Go to website
Last Update: April 17, 2021
License: MIT

Preview:

Simple Extensible Code Editor – Copenhagen

Description:

Copenhagen is a simple, lightweight, extensible code editor that supports popular languages like HTML, CSS, JavaScript, JSON, Markdown, and Plain Text.

Main Features:

  • Easy to implement.
  • Code autocomplete.
  • Syntax highlighting.
  • Line numbers.
  • Custom hotkeys.
  • Multi-cursor selection.
  • Find and replace.

Basic usage:

1. Load the Copenhagen’s JavaScript and CSS files.

<link rel=”stylesheet” href=”./compiled/copenhagen.min.css”>
<script src=”./compiled/copenhagen.min.js”></script>

2. Create a container for the editor and determine which language you want to use: “javascript”, “json”, “markdown”, “html”, “css” and “text”.

<div class="editor" data-language="javascript" data-maxrows="20">
  // JS Code Here
</div>

3. Initialize the Copenhagen editor and done.

window.addEventListener('DOMContentLoaded', function () {
  var editor = Copenhagen.initSelectorAll('.editor');
});

4. All configurations that can be passed via either JavaScript or data-option attributes:

var editor = new Copenhagen.Editor({
    // enable readonly here
    readonly: false,

    // hide the editor
    hidden: false,

    // disable the editor
    booleandisabled: false,

    // determine whether to take up the entirety of the relative parent
    maximized: false,

    // minimum number of rows of the editor
    rows: 1,

    // maximum number of rows of the editor
    rmaxrows: 30,

    // determine whether you can "tab out" of the editor
    tabout: false,

    // disable line numbers here
    nolines: false,

});

5. Add your own autocomplete (suggestions) to the editor.

function CodeCompleter () {
  this.suggestions = this.generateSuggestions(this.suggestionMap);
};
CodeCompleter.prototype.cursorCharacter = '·';
CodeCompleter.prototype.wildcardWordCharacter = '¤';
CodeCompleter.prototype.wildcardPhraseCharacter = '…';
CodeCompleter.prototype.wildcardReplaceCharacter = '\\$1';
CodeCompleter.prototype.suggestionMap = {
  'javascript': [
    'const ',
    'const ¤ = ',
    'const {…} = ',
    'const […] = ',
    'console.log(`·Got here: A·`);',
    'console.error(`·Error·`);',
    'let ',
    'let ¤ = ',
    'let {…} = ',
    'let […] = ',
    'var ',
    'var ¤ = ',
    'var {…} = ',
    'var […] = ',
    'lib.',
    'module.exports = ',
    'module.exports = async ',
    'return ',
    'require(\'·\')',
    'class ',
    'class ¤ {·}',
    'function ',
    'function (·)',
    'function ¤ (·)',
    'function () {·}',
    'function ¤ () {·}',
    'function (…) {·}',
    'function ¤ (…) {·}',
    'if (·true·)',
    'if () {·}',
    'if (…) {·}',
    'else ',
    'else {·}',
    'else if (·true·)',
    'for (let i = 0; i < ·10·; i++)',
    'for () {·}',
    'for (…) {·}',
    'while (·true·)',
    'while () {·}',
    'while (…) {·}',
    'await ',
    'await lib.',
    'await new Promise((resolve, reject) => {·});',
    'async ',
    'async (·)',
    '() => {·}',
    '(…) => {·}',
    '/**\n * ·\n */',
    '* @param {·}',
    '* @param {…} ·paramName·',
    '* @returns {·}',
    '* @returns {…} ·returnValue·',
    'true',
    'false',
    'null',
    'new ',
    'new Promise((resolve, reject) => {·});',
    'Promise((resolve, reject) => {·});',
    'Promise.all([·]);',
    'setTimeout(() => {·}, 1);',
    'setInterval(() => {·}, 1);',
    'try {·}',
    'catch (e) {·}',
    'catch (…) {·}',
    'throw ',
    'throw new Error(`·Oops!·`);',
    'new Error(`·Oops!·`)',
    'Error(`·Oops!·`)',
    'Error(…)'
  ]
};
CodeCompleter.prototype.generateSuggestions = function () {
  var suggestionMap = this.suggestionMap;
  var cursorCharacter = this.cursorCharacter;
  return Object.keys(suggestionMap).reduce(function (suggestions, language) {
    var phraseList = suggestionMap[language].map(function (value) {
      var cursorStart = value.indexOf(cursorCharacter);
      var cursorEnd = value.lastIndexOf(cursorCharacter);
      var cursorLength = 0;
      if (cursorStart !== cursorEnd) {
        cursorLength = cursorEnd - cursorStart - 1;
        value = value.slice(0, cursorEnd) + value.slice(cursorEnd + 1);
      }
      var adjust = cursorStart === -1
        ? 0
        : cursorStart - value.length + 1;
      if (adjust) {
        value = value.substr(0, value.length + adjust - 1) + value.substr(value.length + adjust);
      }
      return {
        value: value,
        adjust: adjust,
        cursorLength: cursorLength
      };
    }.bind(this));
    suggestions[language] = {
      lookup: this.generateLookupTrie(phraseList),
    };
    return suggestions;
  }.bind(this), {});
};
CodeCompleter.prototype.generateLookupTrie = function (phraseList) {
  var wildcardWord = this.wildcardWordCharacter;
  var wildcardPhrase = this.wildcardPhraseCharacter;
  var root = {};
  var curNode, node, phrase, value;
  var i, j, k;
  for (i = 0; i < phraseList.length; i++) {
    phrase = phraseList[i];
    value = phrase.value;
    for (j = value.length - 1; j >= 0; j--) {
      curNode = root;
      for (k = j; k >= 0; k--) {
        char = value[k];
        curNode[char] = curNode[char] || {};
        if (char === wildcardWord || char === wildcardPhrase) {
          curNode[char][char] = curNode[char][char] || curNode[char];
        }
        curNode = curNode[char];
      }
      curNode.phrases = curNode.phrases || [];
      curNode.phrases.push({
        value: value,
        ranking: i,
        adjust: phrase.adjust,
        cursorLength: phrase.cursorLength,
        re: phrase.re
      });
    }
  }
  return root;
};
CodeCompleter.prototype.complete = function (tree, value, index, subs, inWildcard) {
  index = index || 0;
  subs = subs || [];
  inWildcard = inWildcard || '';
  var wildcardWord = this.wildcardWordCharacter;
  var wildcardPhrase = this.wildcardPhraseCharacter;
  var wildcardReplace = this.wildcardReplaceCharacter;
  var char;
  var results = [];
  var node = tree;
  for (var i = value.length - 1; i >= 0; i--) {
    index++;
    var char = value[i];
    if (node[wildcardWord]) {
      if (char.match(/[0-9a-z_$]/i)) {
        var newSubs = subs.slice();
        if (inWildcard) {
          newSubs[0] = char + newSubs[0];
        } else {
          newSubs.unshift(char);
        }
        results = results.concat(
          this.complete(node[wildcardWord], value.substr(0, i), index - 1, newSubs, wildcardWord)
        );
      }
    }
    if (node[wildcardPhrase]) {
      if (char.match(/[^\(\)\[\]\{\}\"\'\`]/i)) {
        var newSubs = subs.slice();
        if (inWildcard) {
          newSubs[0] = char + newSubs[0];
        } else {
          newSubs.unshift(char);
        }
        results = results.concat(
          this.complete(node[wildcardPhrase], value.substr(0, i), index - 1, newSubs, wildcardPhrase)
        );
      }
    }
    if (node[char]) {
      inWildcard = '';
      if (node.phrases && (char === ' ')) {
        results = results.concat(
          node.phrases.map(function (p) {
            var curSubs = subs.slice();
            return {
              value: p.value.replace(
                new RegExp('(' + [wildcardWord, wildcardPhrase, wildcardReplace].join('|') + ')', 'gi'),
                function ($0) { return curSubs.shift() || ''; }
              ),
              ranking: p.ranking,
              adjust: p.adjust,
              offset: index - 1 + subs.join('').length,
              cursorLength: p.cursorLength
            };
          })
        );
      }
      node = node[char];
    } else {
      break;
    }
  }
  if (node.phrases && (i < 0 || value[i] === ' ')) {
    (i < 0) && index++;
    results = results.concat(
      node.phrases.map(function (p) {
        var curSubs = subs.slice();
        return {
          value: p.value.replace(
            new RegExp('(' + [wildcardWord, wildcardPhrase, wildcardReplace].join('|') + ')', 'gi'),
            function ($0) { return curSubs.shift() || ''; }
          ),
          ranking: p.ranking,
          adjust: p.adjust,
          offset: index - 1 + subs.join('').length,
          cursorLength: p.cursorLength
        };
      })
    );
  }
  return results
    .sort(function (p1, p2) { return p2.offset - p1.offset || p1.ranking - p2.ranking; })
    .filter(function (p) { return p.offset < p.value.length; });
};
CodeCompleter.protot

6. API methods.

// Create a custom autocompletion detector to add your own autocomplete box
editor.addAutocomplete(name, function(editor, selection, inString, inComment){
// ...
})

// Add custom hotkeys
editor.addHotkey(keys, function(value, cursors){
// ...
})

// Disable animation
editor.animateNo()

// Blur the editor
editor.blur()

// Enable autocomplete
editor.canSuggest()

// Clear history
editor.clearHistory()

// Clear meta data
editor.clearMetadata(metadata)

// Disable
editor.disable()

// Emulate a user action
editor.emulateUserAction(userAction)

// Enable
editor.enable()

// Show find and replace dialog
editor.find(value)

// Find the complement of a character at a specific index based on the language dictionary
editor.findComplements(value, index)

// Set focus to the editor
editor.focus()

// Retrieve the active formatter function based on the active language
editor.getActiveFormatter()

// Retrieve the currently active language for the editor
editor.getActiveLanguage()

// Retrieve the currently active language dictionary for the editor
editor.getActiveLanguageDictionary()

// Retrieves the formatter function for a language or return the text formatter if it is not found
editor.getFormatter(language)

// Retrieve metadata from the editor
editor.getMetadata(metadata, defaultValue)

// Retrieve the current value of the editor
editor.getValue()

// Navigate the user action history
editor.gotoHistory(amount)

// Determine whether the editor is focused
editor.hasFocus()

// Hide the editor
editor.hide()

// Determine whether a specific character index is within a comment or not based on the language dictionary
editor.inComment(index)

// Determine whether a specific character index is within a string or not based on the language dictionary.
editor.inString(index)

// Retrieve the current read-only status of the editor.
editor.isReadOnly()

// Open the editor instance
editor.open(element, focus, replaceText)

// Remove an autocompletion detector
editor.removeAutocomplete()

// Remove a hotkey handler
editor.removeHotkey(hotkey)

// Render the editor on the next available frame
editor.render(value, forceRender)

// Dispatches a "save" event and creates a history entry if the user has performed an action
editor.save(value)

// Scrolls the editor by a visible "page" amount based on the height of the editor
// "up" or "down"
editor.scrollPage(direction)

// Scrolls to the currently selected text in the editor
editor.scrollToText()

// Scrolls to a specific line index in the editor
editor.scrollToLine(index)

// Scrolls the editor to a specific (x, y) coordinate from the top left of the editor
editor.scrollTo(x, y)

// Select text in the editor at a specific start and end index
editor.select(start, end)

// Set emulation mode.
editor.setEmulationMode(true/false)

// Set an error state in the editor
editor.setError(lineIndex, column)

// Set a custom formatter for a language
editor.setFormatter(language, function(){
// ...
})

// Set the language
editor.setLanguage(language)

// Set maximized mode on the editor so that it takes up all of its relative parent's height
editor.setMaximized(true/false)

// Sets metadata on the editor
editor.setMetadata(metadata, value)

// Set read-only mode on the editor
editor.setReadOnly(true/false)

// Set the value of the editor
editor.setValue(value)

// Show the editor
editor.show()

// Perform a user action
editor.userAction(userAction)

Changelog:

04/17/2021

  • Bugfix

You Might Be Interested In:


Leave a Reply