From 04bb2a5cd2ca74ef519616c131a7c7db36077214 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 19 Nov 2023 13:28:01 +0100 Subject: [PATCH 1/9] Add ui.EDITOR_EOL setting and use ist instead of monaco's detection --- custom/conf/app.example.ini | 3 +++ docs/content/administration/config-cheat-sheet.en-us.md | 1 + modules/setting/ui.go | 2 ++ modules/templates/helper.go | 3 +++ templates/base/head_script.tmpl | 1 + web_src/js/features/codeeditor.js | 7 ++++--- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 18d6fe37a8ba2..384410a3fb195 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1242,6 +1242,9 @@ LEVEL = Info ;; Change the sort type of the explore pages. ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate +;; +;; Default line ending format for the web editor. Either `LF` or `CRLF`. Can be overridden with .editorconfig. +;EDITOR_EOL = LF ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index c9e6a937c3482..e57fd45c3f081 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -232,6 +232,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). - `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest" +- `EDITOR_EOL`: **LF**: Default line ending format for the web editor. Either `LF` or `CRLF`. Can be overridden with .editorconfig. ### UI - Admin (`ui.admin`) diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 31042d3ee0dda..4a6def6cb6393 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -34,6 +34,7 @@ var UI = struct { SearchRepoDescription bool OnlyShowRelevantRepos bool ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` + EditorEol string `ini:"EDITOR_EOL"` Notification struct { MinTimeout time.Duration @@ -82,6 +83,7 @@ var UI = struct { Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, + EditorEol: `LF`, Notification: struct { MinTimeout time.Duration TimeoutStep time.Duration diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 235fd96b73d8f..9fb0d76c3a19f 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -149,6 +149,9 @@ func NewFuncMap() template.FuncMap { "MermaidMaxSourceCharacters": func() int { return setting.MermaidMaxSourceCharacters }, + "EditorEol": func() string { + return setting.UI.EditorEol + }, // ----------------------------------------------------------------- // render diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl index 4a723f63b9c9e..09dc5030a1bc7 100644 --- a/templates/base/head_script.tmpl +++ b/templates/base/head_script.tmpl @@ -42,6 +42,7 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly. modal_confirm: {{ctx.Locale.Tr "modal.confirm"}}, modal_cancel: {{ctx.Locale.Tr "modal.cancel"}}, }, + editorEol: '{{EditorEol}}', }; {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}} window.config.pageData = window.config.pageData || {}; diff --git a/web_src/js/features/codeeditor.js b/web_src/js/features/codeeditor.js index 5f924fd0864cf..4e02528803cbe 100644 --- a/web_src/js/features/codeeditor.js +++ b/web_src/js/features/codeeditor.js @@ -4,6 +4,7 @@ import {onInputDebounce} from '../utils/dom.js'; const languagesByFilename = {}; const languagesByExt = {}; +const {editorEol} = window.config; const baseOptions = { fontFamily: 'var(--fonts-monospace)', @@ -62,7 +63,7 @@ export async function createMonaco(textarea, filename, editorOpts) { const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor'); initLanguages(monaco); - let {language, eol, ...other} = editorOpts; + let {language, eol: editorConfigEol, ...other} = editorOpts; if (!language) language = getLanguage(filename); const container = document.createElement('div'); @@ -121,8 +122,8 @@ export async function createMonaco(textarea, filename, editorOpts) { const model = editor.getModel(); - // Monaco performs auto-detection of dominant EOL in the file, biased towards LF for - // empty files. If there is an editorconfig value, override this detected value. + // Use eol format from editorconfig if present, otherwise fall back to ui.EDITOR_EOL + const eol = editorConfigEol ?? editorEol; if (eol in monaco.editor.EndOfLineSequence) { model.setEOL(monaco.editor.EndOfLineSequence[eol]); } From d39adbb627eb6e65c08856388762e36ffeb328aa Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 19 Nov 2023 13:36:55 +0100 Subject: [PATCH 2/9] Update custom/conf/app.example.ini --- custom/conf/app.example.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 384410a3fb195..540eb34aa6af8 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1243,7 +1243,7 @@ LEVEL = Info ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate ;; -;; Default line ending format for the web editor. Either `LF` or `CRLF`. Can be overridden with .editorconfig. +;; Default line ending format for the web editor. Either "LF" or "CRLF". Can be overridden with .editorconfig. ;EDITOR_EOL = LF ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From ab1e9ca893e32eb2cdb6f303a10571de40441617 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 19 Nov 2023 13:39:08 +0100 Subject: [PATCH 3/9] Update docs/content/administration/config-cheat-sheet.en-us.md --- docs/content/administration/config-cheat-sheet.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index e57fd45c3f081..e815fac55016f 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -232,7 +232,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). - `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest" -- `EDITOR_EOL`: **LF**: Default line ending format for the web editor. Either `LF` or `CRLF`. Can be overridden with .editorconfig. +- `EDITOR_EOL`: **LF**: Default line ending format for the web editor. Either "LF" or "CRLF". Can be overridden with .editorconfig. ### UI - Admin (`ui.admin`) From 586b8d3b8da85a2c399c93542ac34adedbb6ef9f Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 21 Nov 2023 18:18:05 +0100 Subject: [PATCH 4/9] add server-side convertion --- modules/util/string.go | 13 ++++++++++++- modules/util/string_test.go | 8 ++++++++ routers/web/repo/editor.go | 9 ++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/util/string.go b/modules/util/string.go index f2def7b0ece2c..a2f190b76c7b2 100644 --- a/modules/util/string.go +++ b/modules/util/string.go @@ -3,7 +3,10 @@ package util -import "github.com/yuin/goldmark/util" +import ( + "strings" + "github.com/yuin/goldmark/util" +) func isSnakeCaseUpper(c byte) bool { return 'A' <= c && c <= 'Z' @@ -85,3 +88,11 @@ func ToSnakeCase(input string) string { } return util.BytesToReadOnlyString(res) } + +func ConvertToLF(str string) string { + return strings.ReplaceAll(str, "\r", "") +} + +func ConvertToCRLF(str string) string { + return strings.ReplaceAll(ConvertToLF(str), "\n", "\r\n") +} diff --git a/modules/util/string_test.go b/modules/util/string_test.go index 0a4a8bbcfbf9d..eca22564b732d 100644 --- a/modules/util/string_test.go +++ b/modules/util/string_test.go @@ -45,3 +45,11 @@ func TestToSnakeCase(t *testing.T) { assert.Equal(t, expected, ToSnakeCase(input)) } } + +func TestConvertToLF(t *testing.T) { + assert.Equal(t, "\na\nbc\n\n", ConvertToLF("\r\na\r\nb\rc\n\n")) +} + +func TestConvertToCRLF(t *testing.T) { + assert.Equal(t, "\r\na\r\nbc\r\n\r\n", ConvertToCRLF("\r\na\r\nb\rc\n\n")) +} diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 1ad091b70fd9d..f31f1c4c83e0f 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -277,6 +277,13 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b operation = "create" } + content := form.Content + if setting.UI.EditorEol == "CRLF" { + content = util.ConvertToCRLF(content) + } else { + content = utils.ConvertToLF(content) + } + if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ LastCommitID: form.LastCommit, OldBranch: ctx.Repo.BranchName, @@ -287,7 +294,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b Operation: operation, FromTreePath: ctx.Repo.TreePath, TreePath: form.TreePath, - ContentReader: strings.NewReader(form.Content), + ContentReader: strings.NewReader(content), }, }, Signoff: form.Signoff, From c071ad5c40b909fbfedcb9b9f214fa6c17e7e325 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 21 Nov 2023 18:21:57 +0100 Subject: [PATCH 5/9] fix name --- routers/web/repo/editor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index f31f1c4c83e0f..b64daa098046c 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -281,7 +281,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b if setting.UI.EditorEol == "CRLF" { content = util.ConvertToCRLF(content) } else { - content = utils.ConvertToLF(content) + content = util.ConvertToLF(content) } if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ From 2dfcfe005020976842014e33586331639bed42e5 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 21 Nov 2023 18:46:48 +0100 Subject: [PATCH 6/9] respect editorconfig server-side too --- modules/util/string.go | 1 + routers/web/repo/editor.go | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/util/string.go b/modules/util/string.go index a2f190b76c7b2..13d1c29a59ebc 100644 --- a/modules/util/string.go +++ b/modules/util/string.go @@ -5,6 +5,7 @@ package util import ( "strings" + "github.com/yuin/goldmark/util" ) diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index b64daa098046c..a1d17cf84678c 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -277,8 +277,21 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b operation = "create" } + eol := setting.UI.EditorEol + editorconfigEol := "" + ec, _, err := ctx.Repo.GetEditorconfig() + if err == nil { + def, err := ec.GetDefinitionForFilename(form.TreePath) + if err == nil { + editorconfigEol = strings.ToUpper(def.EndOfLine) + if editorconfigEol != "" { + eol = editorconfigEol + } + } + } + content := form.Content - if setting.UI.EditorEol == "CRLF" { + if eol == "CRLF" { content = util.ConvertToCRLF(content) } else { content = util.ConvertToLF(content) From 2fdf6d985d4713c1af11371c5b2d69415046f228 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 21 Nov 2023 23:43:38 +0100 Subject: [PATCH 7/9] fix lint and refactor --- modules/util/string.go | 8 +++++--- modules/util/string_test.go | 8 ++++---- routers/web/repo/editor.go | 12 ++++-------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/util/string.go b/modules/util/string.go index 13d1c29a59ebc..dd2c9b6564c56 100644 --- a/modules/util/string.go +++ b/modules/util/string.go @@ -90,10 +90,12 @@ func ToSnakeCase(input string) string { return util.BytesToReadOnlyString(res) } -func ConvertToLF(str string) string { +// convert all newlines in string to \n +func ToLF(str string) string { return strings.ReplaceAll(str, "\r", "") } -func ConvertToCRLF(str string) string { - return strings.ReplaceAll(ConvertToLF(str), "\n", "\r\n") +// convert all newlines in string to \r\n +func ToCRLF(str string) string { + return strings.ReplaceAll(ToLF(str), "\n", "\r\n") } diff --git a/modules/util/string_test.go b/modules/util/string_test.go index eca22564b732d..720560c70cf06 100644 --- a/modules/util/string_test.go +++ b/modules/util/string_test.go @@ -46,10 +46,10 @@ func TestToSnakeCase(t *testing.T) { } } -func TestConvertToLF(t *testing.T) { - assert.Equal(t, "\na\nbc\n\n", ConvertToLF("\r\na\r\nb\rc\n\n")) +func TestToLF(t *testing.T) { + assert.Equal(t, "\na\nbc\n\n", ToLF("\r\na\r\nb\rc\n\n")) } -func TestConvertToCRLF(t *testing.T) { - assert.Equal(t, "\r\na\r\nbc\r\n\r\n", ConvertToCRLF("\r\na\r\nb\rc\n\n")) +func TestToCRLF(t *testing.T) { + assert.Equal(t, "\r\na\r\nbc\r\n\r\n", ToCRLF("\r\na\r\nb\rc\n\n")) } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index a1d17cf84678c..79335de6dda4b 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -278,23 +278,19 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b } eol := setting.UI.EditorEol - editorconfigEol := "" ec, _, err := ctx.Repo.GetEditorconfig() if err == nil { def, err := ec.GetDefinitionForFilename(form.TreePath) if err == nil { - editorconfigEol = strings.ToUpper(def.EndOfLine) - if editorconfigEol != "" { - eol = editorconfigEol - } + eol = strings.ToUpper(def.EndOfLine) } } content := form.Content if eol == "CRLF" { - content = util.ConvertToCRLF(content) - } else { - content = util.ConvertToLF(content) + content = util.ToCRLF(content) + } else { // convert to LF, this was hardcoded before 1.21 + content = util.ToLF(content) } if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ From 6aaee670803a3e2163d4aaca1ff9e0b7d6a4d753 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 21 Nov 2023 23:45:29 +0100 Subject: [PATCH 8/9] reword docs --- custom/conf/app.example.ini | 2 +- docs/content/administration/config-cheat-sheet.en-us.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 540eb34aa6af8..9780822e07838 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1243,7 +1243,7 @@ LEVEL = Info ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate ;; -;; Default line ending format for the web editor. Either "LF" or "CRLF". Can be overridden with .editorconfig. +;; Newline format for the web editor. Either "LF" or "CRLF". Can be overridden per-file with .editorconfig. ;EDITOR_EOL = LF ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index e815fac55016f..48053ddebfd2e 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -232,7 +232,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). - `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest" -- `EDITOR_EOL`: **LF**: Default line ending format for the web editor. Either "LF" or "CRLF". Can be overridden with .editorconfig. +- `EDITOR_EOL`: **LF**: Newline format for the web editor. Either "LF" or "CRLF". Can be overridden per-file with .editorconfig. ### UI - Admin (`ui.admin`) From c321a6847c1767dc8cd623cc6581c89a76204437 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 22 Nov 2023 00:10:19 +0100 Subject: [PATCH 9/9] pass setting via data attribute --- templates/base/head_script.tmpl | 1 - templates/repo/editor/edit.tmpl | 3 ++- web_src/js/features/codeeditor.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl index 09dc5030a1bc7..4a723f63b9c9e 100644 --- a/templates/base/head_script.tmpl +++ b/templates/base/head_script.tmpl @@ -42,7 +42,6 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly. modal_confirm: {{ctx.Locale.Tr "modal.confirm"}}, modal_cancel: {{ctx.Locale.Tr "modal.cancel"}}, }, - editorEol: '{{EditorEol}}', }; {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}} window.config.pageData = window.config.pageData || {}; diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 236f10bb0ad4e..3b339eb8b2529 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -40,7 +40,8 @@ data-context="{{.RepoLink}}" data-previewable-extensions="{{.PreviewableExtensions}}" data-line-wrap-extensions="{{.LineWrapExtensions}}" - data-initial-value="{{JsonUtils.EncodeToString .FileContent}}"> + data-initial-value="{{JsonUtils.EncodeToString .FileContent}}" + data-editor-eol="{{EditorEol}}">
diff --git a/web_src/js/features/codeeditor.js b/web_src/js/features/codeeditor.js index 4e02528803cbe..a9e1d2eecfac3 100644 --- a/web_src/js/features/codeeditor.js +++ b/web_src/js/features/codeeditor.js @@ -4,7 +4,6 @@ import {onInputDebounce} from '../utils/dom.js'; const languagesByFilename = {}; const languagesByExt = {}; -const {editorEol} = window.config; const baseOptions = { fontFamily: 'var(--fonts-monospace)', @@ -123,7 +122,8 @@ export async function createMonaco(textarea, filename, editorOpts) { const model = editor.getModel(); // Use eol format from editorconfig if present, otherwise fall back to ui.EDITOR_EOL - const eol = editorConfigEol ?? editorEol; + const editorEol = textarea.getAttribute('data-editor-eol'); + const eol = editorConfigEol || editorEol || "LF"; if (eol in monaco.editor.EndOfLineSequence) { model.setEOL(monaco.editor.EndOfLineSequence[eol]); }