Open zuznow opened 7 years ago
Status?
Status?
Only every other editor supports this, I guess it's not important. </sarcasm>
In VSCode we wait with adding the closing tag until you type the >
.
That's something to port to the Monaco editor. PRs welcome...
I guess tagClosing.ts needs to be ported to monaco-html
@mofux Yes, exactly.
Status?
Could be something like this:
editor.onKeyDown(function(e) {
if(e.keyCode == 84 && editor.getModel()._languageIdentifier.language == "xml"){
var position = editor.getPosition();
var text = editor.getValue(position);
var splitedText= text.split("\n");
var line = splitedText[position.lineNumber-1];
var regex = /<(\w+)[^\/>]*$/;
if (line.match(regex)) {
var content = "</"+line.match(regex)[1]+">";
editor.trigger('bla', 'type', { text: content});
editor.setPosition(position);
}
}
}, 'editorTextFocus && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible')
@TortoiseMaster regex for parsing XML...
wow 2020 and still this issue not solved??
wow 2020 and still this issue not solved??
Yes, most of the people working on this project are interested in more important issues
if they ever complete it (which i highly doubt now :P), they better make the tab push in it so i can get a general syntax autocomplete after tab.
Seems none of the maintainers really care about bringing Monaco up to date with other editors...
@TortoiseMaster any progress with your code? i noticed that even style & script syntax are missing when using html language mode.. now i have to think whether to add them manually going through docs or switch to any better alternative sigh.
@aloksharma1 Now it looks like this:
function onXMLKeyDownListener(editor)
{
function isBracketClose(event) {
return (event.browserEvent && event.browserEvent.key == ">") || (event.keyCode == 84 && e.shiftKey);
}
return function(e)
{
// console.log("EVENT ", e, isQuote(e), isBracketClose(e))
if (isBracketClose(e) /* > */){
const position = editor.getPosition();
const text = editor.getValue(position);
const splitedText= text.split("\n");
const line = splitedText[position.lineNumber-1];
let preLine = line.substring(0, position.column-1);
let postLine = line.substring(position.column-1);
let i = 1;
while (preLine.indexOf("<") == -1 && position.lineNumber - i >= 0) {
preLine = splitedText[position.lineNumber - 1 - i] + preLine;
i++;
}
const regex = /<(\w+)[^\/>]*$/;
if (preLine.match(regex) && !(postLine.indexOf(">") < postLine.indexOf("<"))) {
let content = "</"+preLine.match(regex)[1]+">";
editor.trigger('bla', 'type', {text: content});
editor.setPosition(position);
}
}
}
}
My solution.
import monaco from 'monaco-editor/esm/vs/editor/editor.api';
monaco.languages.registerCompletionItemProvider('html', {
triggerCharacters: ['>'],
provideCompletionItems: (model, position) => {
const codePre: string = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const tag = codePre.match(/.*<(\w+)>$/)?.[1];
if (!tag) {
return {};
}
const word = model.getWordUntilPosition(position);
return {
suggestions: [
{
label: `</${tag}>`,
kind: monaco.languages.CompletionItemKind.EnumMember,
insertText: `</${tag}>`,
range: {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
},
},
],
};
},
});
My solution.
import monaco from 'monaco-editor/esm/vs/editor/editor.api'; monaco.languages.registerCompletionItemProvider('html', { triggerCharacters: ['>'], provideCompletionItems: (model, position) => { const codePre: string = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); const tag = codePre.match(/.*<(\w+)>$/)?.[1]; if (!tag) { return {}; } const word = model.getWordUntilPosition(position); return { suggestions: [ { label: `</${tag}>`, kind: monaco.languages.CompletionItemKind.EnumMember, insertText: `</${tag}>`, range: { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn, }, }, ], }; }, });
Thanks, @EmiyaYang. I was getting some errors on my console, when I'm just typing on the editor without starting with <
errors.ts:22 Uncaught Error: i.suggestions is not iterable
TypeError: i.suggestions is not iterable
at b (suggest.ts:190)
at Object.<anonymous> (suggest.ts:238)
at Generator.next (<anonymous>)
at r (compressedObjectTreeModel.ts:457)
at errors.ts:22
So, I replace return {}
with just return
in the if word doesn't match tag regexp.
...
const tag = codePre.match(/.*<(\w+)>$/)?.[1];
if (!tag) {
- return {};
+ return
}
const word = model.getWordUntilPosition(position);
...
Here is my solution, which feels a lot like vscode (which is inspired by @EmiyaYang):
const disposable =
model &&
model.onDidChangeContent((e) => {
!e.isRedoing &&
!e.isUndoing &&
e.changes
.filter(({ text }) => text.lastIndexOf(">") !== -1 && text.lastIndexOf(">") === text.length - 1)
.forEach(({ range, rangeLength }) => {
const untilLine = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: range.endLineNumber,
endColumn: range.endColumn + rangeLength + 1,
});
// look for return, arrow function and variable assignment first --> don't complete when using generics
const enclosingTag = /(?:return[\S\s]*|\s?=\s?|=>.*)<(\w+)>(?:\s*})?$/.exec(untilLine)?.[1];
if (!enclosingTag || enclosingTag.includes("/")) {
return;
}
const newRange = new monaco.Range(
range.endLineNumber,
range.endColumn + rangeLength + 1,
range.endLineNumber,
range.endColumn + rangeLength + enclosingTag.length + 1
);
// save content which will be overridden otherwise
const rest = model.getValueInRange(newRange);
model.applyEdits([
{
range: newRange,
text: `</${enclosingTag}>${rest}`,
},
]);
});
});
The solution tries to avoid completing generics. Use disposable && disposable.dispose();
if the listener is not used anymore. You just need the model
you want to use it on.
I modified @EmiyaYang solution to also move the cursor inside the tags when the suggestion is selected.
`
monaco.languages.registerCompletionItemProvider('html',
{
triggerCharacters: ['>'],
provideCompletionItems: (model, position) =>
{
const codePre: string = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const tag = codePre.match(/.*<(\w+)>$/)?.[1];
if (!tag) {
return;
}
const word = model.getWordUntilPosition(position);
return {
suggestions: [
{
label: `</${tag}>`,
kind: monaco.languages.CompletionItemKind.EnumMember,
insertText: `$1</${tag}>`,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range: {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
},
},
],
};
},
});
`
Here is my implementation:
import isSelfClosing from 'is-self-closing';
editor.onKeyDown((event) => {
// when the user enters '>'
if (event.browserEvent.key === '>') {
const model = editor.getModel();
const currentSelections = editor.getSelections();
const edits = [];
const newSelections = [];
// potentially insert the ending tag at each of the selections
for (const selection of currentSelections) {
// shift the selection over by one to account for the new character
newSelections.push(new Selection(
selection.selectionStartLineNumber,
selection.selectionStartColumn + 1,
selection.endLineNumber,
selection.endColumn + 1
));
// grab the line before the cursor
const lineBeforeChange = model.getValueInRange({
startLineNumber: selection.endLineNumber,
startColumn: 1,
endLineNumber: selection.endLineNumber,
endColumn: selection.endColumn,
});
// if ends with a HTML tag we are currently closing
const tag = lineBeforeChange.match(/.*<([\w-]+)$/)?.[1];
if (!tag) {
continue;
}
// skip self-closing tags like <br> or <img>
if (isSelfClosing(tag)) {
continue;
}
// add in the closing tag
edits.push({
range: {
startLineNumber: selection.endLineNumber,
startColumn: selection.endColumn + 1, // add 1 to offset for the inserting '>' character
endLineNumber: selection.endLineNumber,
endColumn: selection.endColumn + 1,
},
text: `</${tag}>`,
});
}
// wait for next tick to avoid it being an invalid operation
setTimeout(() => {
editor.executeEdits(
model.getValue(),
edits,
newSelections
);
}, 0);
}
})
This is a modification of the code by @avigoldman
editor.onKeyDown((event) => {
// select enabled languages
const enabledLanguages = ["html", "markdown", "javascript", "typescript"]; // enable js & ts for jsx & tsx
const model = editor.getModel();
if (!enabledLanguages.includes(model.getLanguageId())) {
return;
}
const isSelfClosing = (tag) =>
[
"area",
"base",
"br",
"col",
"command",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr",
"circle",
"ellipse",
"line",
"path",
"polygon",
"polyline",
"rect",
"stop",
"use",
].includes(tag);
// when the user enters '>'
if (event.browserEvent.key === ">") {
const currentSelections = editor.getSelections();
const edits = [];
const newSelections = [];
// potentially insert the ending tag at each of the selections
for (const selection of currentSelections) {
// shift the selection over by one to account for the new character
newSelections.push(
new monaco.Selection(
selection.selectionStartLineNumber,
selection.selectionStartColumn + 1,
selection.endLineNumber,
selection.endColumn + 1,
),
);
// grab the content before the cursor
const contentBeforeChange = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: selection.endLineNumber,
endColumn: selection.endColumn,
});
// if ends with a HTML tag we are currently closing
const match = contentBeforeChange.match(/<([\w-]+)(?![^>]*\/>)[^>]*$/);
if (!match) {
continue;
}
const [fullMatch, tag] = match;
// skip self-closing tags like <br> or <img>
if (isSelfClosing(tag) || fullMatch.trim().endsWith("/")) {
continue;
}
// add in the closing tag
edits.push({
range: {
startLineNumber: selection.endLineNumber,
startColumn: selection.endColumn + 1, // add 1 to offset for the inserting '>' character
endLineNumber: selection.endLineNumber,
endColumn: selection.endColumn + 1,
},
text: `</${tag}>`,
});
}
// wait for next tick to avoid it being an invalid operation
setTimeout(() => {
editor.executeEdits(model.getValue(), edits, newSelections);
}, 0);
}
});
modifications:
<div />
)<div id="my-id" class="hello">
<div
id="my-id"
class="hello"
>
try it in LiveCodes playground
edit: changed regular expression to improve performance
This is my solution for TypeScript with Next.js
Hi There is a way to configure HTML autocomplete I would like that it will add the close tag as done in other editors Thanks Orly
monaco-editor npm version: 0.5.3 Browser: N/A OS: N/A