in src/executable-code/executable-fragment.js [639:874]
initializeCodeMirror(options = {}) {
const textarea = this.nodes[0].getElementsByTagName('textarea')[0];
const readOnly = options.highlightOnly;
const codemirrorOptions = {
readOnly: readOnly,
lineNumbers: false,
mode: options.mode,
theme: options.theme,
matchBrackets: options.matchBrackets,
scrollbarStyle: options.scrollbarStyle || 'overlay',
continueComments: true,
autoCloseBrackets: true,
indentUnit: options.indent,
viewportMargin: Infinity,
foldGutter: true,
gutters: [SELECTORS.ERROR_AND_WARNING_GUTTER, SELECTORS.FOLD_GUTTER],
};
// Workaround to allow copy code in read-only mode
// Taken from https://github.com/codemirror/CodeMirror/issues/2568#issuecomment-308137063
if (readOnly) {
codemirrorOptions.cursorBlinkRate = -1;
}
this.codemirror = CodeMirror.fromTextArea(textarea, codemirrorOptions);
// don't need to create additional editor options in readonly mode.
if (readOnly) return;
/**
* Show highlight for extraKey Ctrl+Alt+H/Cmd+Option+H
*/
let highlight = () => {
const { compilerVersion, targetPlatform, hiddenDependencies } =
this.state;
this.removeStyles();
WebDemoApi.getHighlight(
this.getCode(),
compilerVersion,
targetPlatform,
hiddenDependencies,
).then((data) => this.showDiagnostics(data));
};
const showImportSuggestions = (mirror) => {
if (this.importsSuggestions.length === 0) return;
let cur = mirror.getCursor();
let token = mirror.getTokenAt(cur);
let interval = {
start: { line: cur.line, ch: token.start },
end: { line: cur.line, ch: token.end },
};
let results = this.importsSuggestions
.filter((it) => equal(it.interval, interval))
.map((it) => it.imports)
.flat();
let withImports = this.canAddImport;
if (results.length !== 0) {
let options = {
hint: function () {
return {
from: mirror.getDoc().getCursor(),
to: mirror.getDoc().getCursor(),
list: results.map((result) => {
if (!withImports) {
result[IMPORT_NAME] = null;
}
return new CompletionView(result);
}),
};
},
};
mirror.showHint(options);
}
};
const hint = (mirror, callback) => {
let cur = mirror.getCursor();
let token = mirror.getTokenAt(cur);
let code = this.state.folded
? this.prefix + mirror.getValue() + this.suffix
: mirror.getValue();
let currentCursor = this.state.folded
? { line: cur.line + this.prefix.split('\n').length - 1, ch: cur.ch }
: cur;
WebDemoApi.getAutoCompletion(
code,
currentCursor,
this.state.compilerVersion,
this.state.targetPlatform,
this.state.hiddenDependencies,
processingCompletionsList,
);
let withImports = this.canAddImport;
function processingCompletionsList(results) {
const anchorCharPosition = mirror.findWordAt({
line: cur.line,
ch: cur.ch,
}).anchor.ch;
const headCharPosition = mirror.findWordAt({
line: cur.line,
ch: cur.ch,
}).head.ch;
const currentSymbol = mirror.getRange(
{ line: cur.line, ch: anchorCharPosition },
{
line: cur.line,
ch: headCharPosition,
},
);
if (results.length === 0 && /^[a-zA-Z]+$/.test(currentSymbol)) {
CodeMirror.showHint(mirror, CodeMirror.hint.auto, {
completeSingle: false,
});
} else {
callback({
list: results.map((result) => {
if (!withImports) result[IMPORT_NAME] = null;
return new CompletionView(result);
}),
from: { line: cur.line, ch: token.start },
to: { line: cur.line, ch: token.end },
});
}
}
};
CodeMirror.registerHelper('hint', 'kotlin', hint);
CodeMirror.hint.kotlin.async = true;
CodeMirror.commands.autocomplete = (cm) => {
cm.showHint(cm);
};
/**
* Register own helper for autocomplete.
* Getting completions from api.kotlinlang.org.
* CodeMirror.hint.default => getting list from codemirror kotlin keywords.
*
* {@see WebDemoApi} - getting data from WebDemo
* {@see CompletionView} - implementation completion view
*/
this.codemirror.setOption('hintOptions', { hint });
if (window.navigator.appVersion.indexOf(MAC) !== -1) {
this.codemirror.setOption('extraKeys', {
'Cmd-Alt-L': 'indentAuto',
'Shift-Tab': 'indentLess',
'Ctrl-/': 'toggleComment',
'Cmd-[': false,
'Cmd-]': false,
'Ctrl-Space': 'autocomplete',
'Cmd-Alt-H': highlight,
'Cmd-Alt-Enter': debounce(showImportSuggestions, DEBOUNCE_TIME),
});
} else {
this.codemirror.setOption('extraKeys', {
'Ctrl-Alt-L': 'indentAuto',
'Shift-Tab': 'indentLess',
'Ctrl-/': 'toggleComment',
'Ctrl-[': false,
'Ctrl-]': false,
'Ctrl-Space': 'autocomplete',
'Ctrl-Alt-H': highlight,
'Ctrl-Alt-Enter': debounce(showImportSuggestions, DEBOUNCE_TIME),
});
}
/**
* When editor's changed:
* 1) Remove all styles
* 2) if onFlyHighLight flag => getting highlight
*/
this.codemirror.on(
'change',
debounce((cm) => {
const {
onChange,
onFlyHighLight,
compilerVersion,
targetPlatform,
hiddenDependencies,
} = this.state;
if (onChange) onChange(cm.getValue());
this.removeStyles();
if (onFlyHighLight) {
WebDemoApi.getHighlight(
this.getCode(),
compilerVersion,
targetPlatform,
hiddenDependencies,
).then((data) => this.showDiagnostics(data));
}
}, DEBOUNCE_TIME),
);
/**
* If autoComplete => Getting completion on every key press on the editor.
*/
this.codemirror.on(
'keypress',
debounce((cm, event) => {
if (event.keyCode !== KEY_CODES.R && !event.ctrlKey) {
if (this.state.autoComplete && !cm.state.completionActive) {
CodeMirror.showHint(cm, CodeMirror.hint.kotlin, {
completeSingle: false,
});
}
}
}, DEBOUNCE_TIME),
);
/**
* Select marker's placeholder on mouse click
*/
this.codemirror.on('mousedown', (codemirror, event) => {
let position = codemirror.coordsChar({
left: event.pageX,
top: event.pageY,
});
if (position.line !== 0 || position.ch !== 0) {
let markers = codemirror.findMarksAt(position);
let todoMarker = markers.find(
(marker) => marker.className === SELECTORS.MARK_PLACEHOLDER,
);
if (todoMarker != null) {
let markerPosition = todoMarker.find();
codemirror.setSelection(markerPosition.from, markerPosition.to);
codemirror.focus();
event.preventDefault();
}
}
});
}