
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.protot6. 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







