Skip to content

Switch code editor to Monaco #11366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1f3dbed
Switch code editor to Monaco
silverwind May 10, 2020
c752908
inline editorconfig, fix diff, use for markdown, remove more dead code
silverwind May 11, 2020
e98b1c9
refactors, remove jquery usage
silverwind May 11, 2020
61702bb
use tab_width
silverwind May 11, 2020
0529866
fix intellisense
silverwind May 11, 2020
ec541d0
rename function for clarity
silverwind May 11, 2020
800b871
misc tweaks, enable webpack progress display
silverwind May 11, 2020
201dc7b
only use --progress on dev build
silverwind May 11, 2020
7620465
remove useless borders in arc-green
silverwind May 11, 2020
f0becfe
fix typo
silverwind May 11, 2020
749116c
remove obsolete comment
silverwind May 12, 2020
1ab5997
small refactor
silverwind May 12, 2020
c8b3fb2
fix file creation and various refactors
silverwind May 13, 2020
b88e4f9
unset useTabStops too when no editorconfig
silverwind May 13, 2020
09d6f0a
small refactor
silverwind May 13, 2020
48d4a94
disable webpack's [big] warnings
silverwind May 13, 2020
686eadf
remove useless await
silverwind May 13, 2020
91c1265
fix dark theme check
silverwind May 13, 2020
d992c88
rename chunk to 'monaco'
silverwind May 13, 2020
4eff6cf
add to .gitignore and delete webpack dest before build
silverwind May 13, 2020
89d2b55
increase editor height
silverwind May 13, 2020
a671949
support more editorconfig properties
silverwind May 13, 2020
acc4657
remove empty element filter
silverwind May 13, 2020
7e8a3b4
rename
silverwind May 13, 2020
0a641bd
Merge branch 'master' into monaco
jolheiser May 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ rules:
no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, ignoreRestSiblings: true}]
no-use-before-define: [0]
no-var: [2]
object-curly-newline: [0]
object-curly-spacing: [2, never]
one-var-declaration-per-line: [0]
one-var: [0]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ coverage.all
/yarn.lock
/public/js
/public/css
/public/fonts
/public/fomantic
/public/img/svg
/VERSION
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(fi
WEBPACK_SOURCES := $(shell find web_src/js web_src/less -type f)
WEBPACK_CONFIGS := webpack.config.js
WEBPACK_DEST := public/js/index.js public/css/index.css
WEBPACK_DEST_DIRS := public/js public/css
WEBPACK_DEST_DIRS := public/js public/css public/fonts

BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
Expand Down Expand Up @@ -295,7 +295,7 @@ lint-frontend: node_modules

.PHONY: watch-frontend
watch-frontend: node_modules
NODE_ENV=development npx webpack --hide-modules --display-entrypoints=false --watch
NODE_ENV=development npx webpack --hide-modules --display-entrypoints=false --watch --progress

.PHONY: test
test:
Expand Down Expand Up @@ -598,6 +598,7 @@ $(FOMANTIC_DEST): $(FOMANTIC_CONFIGS) package-lock.json | node_modules
webpack: $(WEBPACK_DEST)

$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json | node_modules
rm -rf $(WEBPACK_DEST_DIRS)
npx webpack --hide-modules --display-entrypoints=false
@touch $(WEBPACK_DEST)

Expand Down
2 changes: 1 addition & 1 deletion custom/conf/app.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki
PREFIX_ARCHIVE_FILES = true

[repository.editor]
; List of file extensions for which lines should be wrapped in the CodeMirror editor
; List of file extensions for which lines should be wrapped in the Monaco editor
; Separate extensions with a comma. To line wrap files without an extension, just put a comma
LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
; Valid file modes that have a preview API associated with them, such as api/v1/markdown
Expand Down
48 changes: 41 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"domino": "2.1.5",
"dropzone": "5.7.0",
"fast-glob": "3.2.2",
"file-loader": "6.0.0",
"fomantic-ui": "2.8.4",
"highlight.js": "10.0.2",
"imports-loader": "0.8.0",
Expand All @@ -27,6 +28,8 @@
"jquery.are-you-sure": "1.9.0",
"less-loader": "6.0.0",
"mini-css-extract-plugin": "0.9.0",
"monaco-editor": "0.20.0",
"monaco-editor-webpack-plugin": "1.9.0",
"optimize-css-assets-webpack-plugin": "5.0.3",
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0",
Expand All @@ -35,7 +38,7 @@
"svgo": "1.3.2",
"svgo-loader": "2.2.1",
"swagger-ui": "3.25.1",
"terser-webpack-plugin": "3.0.0",
"terser-webpack-plugin": "3.0.1",
"vue": "2.6.11",
"vue-bar-graph": "1.2.0",
"vue-calendar-heatmap": "0.8.4",
Expand Down
17 changes: 16 additions & 1 deletion routers/repo/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package repo

import (
"encoding/json"
"fmt"
"io/ioutil"
"path"
Expand Down Expand Up @@ -146,11 +147,24 @@ func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
ctx.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, ctx.Repo.Repository.FullName())
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, treePath)

ctx.HTML(200, tplEditFile)
}

// GetEditorConfig returns a editorconfig JSON string for given treePath or "null"
func GetEditorConfig(ctx *context.Context, treePath string) string {
ec, err := ctx.Repo.GetEditorconfig()
if err == nil {
def, err := ec.GetDefinitionForFilename(treePath)
if err == nil {
jsonStr, _ := json.Marshal(def)
return string(jsonStr)
}
}
return "null"
}

// EditFile render edit file page
func EditFile(ctx *context.Context) {
editFile(ctx, false)
Expand Down Expand Up @@ -186,6 +200,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, form.TreePath)

if ctx.HasError() {
ctx.HTML(200, tplEditFile)
Expand Down
2 changes: 1 addition & 1 deletion templates/base/head.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{.Language}}">
<html lang="{{.Language}}" class="theme-{{.SignedUser.Theme}}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to handle non-login user?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think anonymous users have access to code edditor

Copy link
Member Author

@silverwind silverwind May 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

he's right, condition should be improved as even unsigned users should have a theme, just not in this variable. Right now it does not matter but I want to move the theme to CSS variables using this class so it has to work then.

<head data-suburl="{{AppSubUrl}}">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
Expand Down
12 changes: 11 additions & 1 deletion templates/pwa/serviceworker_js.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,17 @@ var urlsToCache = [
'{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-regular.woff2',
'{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-italic.woff2',
'{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-700.woff2',
'{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-700italic.woff2'
'{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-700italic.woff2',

// monaco
'{{StaticUrlPrefix}}/css/monaco.css',
'{{StaticUrlPrefix}}/fonts/codicon.ttf',
'{{StaticUrlPrefix}}/js/monaco-css.worker.js',
'{{StaticUrlPrefix}}/js/monaco-editor.worker.js',
'{{StaticUrlPrefix}}/js/monaco-html.worker.js',
'{{StaticUrlPrefix}}/js/monaco-json.worker.js',
'{{StaticUrlPrefix}}/js/monaco.js',
'{{StaticUrlPrefix}}/js/monaco-ts.worker.js'
];

self.addEventListener('install', function (event) {
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/editor/diff_preview.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="diff-file-box">
<div class="ui attached table segment">
<div class="file-body file-code code-view code-diff">
<div class="file-body file-code code-view code-diff-unified">
<table>
<tbody>
{{template "repo/diff/section_unified" dict "file" .File "root" $}}
Expand Down
7 changes: 5 additions & 2 deletions templates/repo/editor/edit.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
{{range $i, $v := .TreeNames}}
<div class="divider"> / </div>
{{if eq $i $l}}
<input id="file-name" value="{{$v}}" placeholder="{{$.i18n.Tr "repo.editor.name_your_file"}}" data-ec-url-prefix="{{$.EditorconfigURLPrefix}}" required autofocus>
<input id="file-name" value="{{$v}}" placeholder="{{$.i18n.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.Editorconfig}}" required autofocus>
<span class="poping up" data-content="{{$.i18n.Tr "repo.editor.filename_help"}}" data-position="bottom center" data-variation="tiny inverted">{{svg "octicon-info" 16}}</span>
{{else}}
<span class="section"><a href="{{EscapePound $.BranchLink}}/{{index $.TreePaths $i | EscapePound}}">{{$v}}</a></span>
Expand All @@ -41,11 +41,14 @@
data-markdown-file-exts="{{.MarkdownFileExts}}"
data-line-wrap-extensions="{{.LineWrapExtensions}}">
{{.FileContent}}</textarea>
<div class="editor-loading">
{{.i18n.Tr "loading"}}
</div>
</div>
<div class="ui bottom attached tab segment markdown" data-tab="preview">
{{.i18n.Tr "loading"}}
</div>
<div class="ui bottom attached tab segment diff" data-tab="diff">
<div class="ui bottom attached tab segment diff edit-diff" data-tab="diff">
{{.i18n.Tr "loading"}}
</div>
</div>
Expand Down
104 changes: 104 additions & 0 deletions web_src/js/features/codeeditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {basename, extname, isObject, isDarkTheme} from '../utils.js';

const languagesByFilename = {};
const languagesByExt = {};

function getEditorconfig(input) {
try {
return JSON.parse(input.dataset.editorconfig);
} catch (_err) {
return null;
}
}

function initLanguages(monaco) {
for (const {filenames, extensions, id} of monaco.languages.getLanguages()) {
for (const filename of filenames || []) {
languagesByFilename[filename] = id;
}
for (const extension of extensions || []) {
languagesByExt[extension] = id;
}
}
}

function getLanguage(filename) {
return languagesByFilename[filename] || languagesByExt[extname(filename)] || 'plaintext';
}

function updateEditor(monaco, editor, filenameInput) {
const newFilename = filenameInput.value;
editor.updateOptions(getOptions(filenameInput));
const model = editor.getModel();
const language = model.getModeId();
const newLanguage = getLanguage(newFilename);
if (language !== newLanguage) monaco.editor.setModelLanguage(model, newLanguage);
}

export async function createCodeEditor(textarea, filenameInput, previewFileModes) {
const filename = basename(filenameInput.value);
const previewLink = document.querySelector('a[data-tab=preview]');
const markdownExts = (textarea.dataset.markdownFileExts || '').split(',');
const lineWrapExts = (textarea.dataset.lineWrapExtensions || '').split(',');
const isMarkdown = markdownExts.includes(extname(filename));

if (previewLink) {
if (isMarkdown && (previewFileModes || []).includes('markdown')) {
previewLink.dataset.url = previewLink.dataset.url.replace(/(.*)\/.*/i, `$1/markdown`);
previewLink.style.display = '';
} else {
previewLink.style.display = 'none';
}
}

const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor');
initLanguages(monaco);

const container = document.createElement('div');
container.className = 'monaco-editor-container';
textarea.parentNode.appendChild(container);

const editor = monaco.editor.create(container, {
value: textarea.value,
language: getLanguage(filename),
...getOptions(filenameInput, lineWrapExts),
});

const model = editor.getModel();
model.onDidChangeContent(() => {
textarea.value = editor.getValue();
textarea.dispatchEvent(new Event('change')); // seems to be needed for jquery-are-you-sure
});

window.addEventListener('resize', () => {
editor.layout();
});

filenameInput.addEventListener('keyup', () => {
updateEditor(monaco, editor, filenameInput);
});

const loading = document.querySelector('.editor-loading');
if (loading) loading.remove();

return editor;
}

function getOptions(filenameInput, lineWrapExts) {
const ec = getEditorconfig(filenameInput);
const theme = isDarkTheme() ? 'vs-dark' : 'vs';
const wordWrap = (lineWrapExts || []).includes(extname(filenameInput.value)) ? 'on' : 'off';

const opts = {theme, wordWrap};
if (isObject(ec)) {
opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec);
if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size);
if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize;
if ('max_line_length' in ec) opts.rulers = [Number(ec.max_line_length)];
opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true;
opts.insertSpaces = ec.indent_style === 'space';
opts.useTabStops = ec.indent_style === 'tab';
}

return opts;
}
Loading