diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go
index 568035fbb7..ed0548578a 100644
--- a/modules/highlight/highlight.go
+++ b/modules/highlight/highlight.go
@@ -166,6 +166,11 @@ func File(numLines int, fileName string, code []byte) map[int]string {
 	}
 
 	htmlw.Flush()
+	finalNewLine := false
+	if len(code) > 0 {
+		finalNewLine = code[len(code)-1] == '\n'
+	}
+
 	m := make(map[int]string, numLines)
 	for k, v := range strings.SplitN(htmlbuf.String(), "\n", numLines) {
 		line := k + 1
@@ -173,9 +178,17 @@ func File(numLines int, fileName string, code []byte) map[int]string {
 		//need to keep lines that are only \n so copy/paste works properly in browser
 		if content == "" {
 			content = "\n"
+		} else if content == `` {
+			content += "\n"
 		}
+		content = strings.TrimSuffix(content, ``)
+		content = strings.TrimPrefix(content, ``)
 		m[line] = content
 	}
+	if finalNewLine {
+		m[numLines+1] = "\n"
+	}
+
 	return m
 }
 
diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go
new file mode 100644
index 0000000000..7c5afaa52c
--- /dev/null
+++ b/modules/highlight/highlight_test.go
@@ -0,0 +1,103 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package highlight
+
+import (
+	"reflect"
+	"testing"
+
+	"code.gitea.io/gitea/modules/setting"
+	"gopkg.in/ini.v1"
+)
+
+func TestFile(t *testing.T) {
+	setting.Cfg = ini.Empty()
+	tests := []struct {
+		name     string
+		numLines int
+		fileName string
+		code     string
+		want     map[int]string
+	}{
+		{
+			name:     ".drone.yml",
+			numLines: 12,
+			fileName: ".drone.yml",
+			code: `kind: pipeline
+name: default
+
+steps:
+- name: test
+	image: golang:1.13
+	environment:
+		GOPROXY: https://goproxy.cn
+	commands:
+	- go get -u
+	- go build -v
+	- go test -v -race -coverprofile=coverage.txt -covermode=atomic
+`,
+			want: map[int]string{
+				1: `kind: pipeline`,
+				2: `name: default`,
+				3: `
+`,
+				4: `steps:`,
+				5: `- name: test`,
+				6: `	image: golang:1.13`,
+				7: `	environment:`,
+				8: `		GOPROXY: https://goproxy.cn`,
+				9: `	commands:`,
+				10: `	- go get -u`,
+				11: `	- go build -v`,
+				12: `	- go test -v -race -coverprofile=coverage.txt -covermode=atomic
+`,
+				13: `
+`,
+			},
+		},
+		{
+			name:     ".drone.yml - trailing space",
+			numLines: 13,
+			fileName: ".drone.yml",
+			code: `kind: pipeline
+name: default  ` + `
+
+steps:
+- name: test
+	image: golang:1.13
+	environment:
+		GOPROXY: https://goproxy.cn
+	commands:
+	- go get -u
+	- go build -v
+	- go test -v -race -coverprofile=coverage.txt -covermode=atomic
+	`,
+			want: map[int]string{
+				1: `kind: pipeline`,
+				2: `name: default  `,
+				3: `
+`,
+				4: `steps:`,
+				5: `- name: test`,
+				6: `	image: golang:1.13`,
+				7: `	environment:`,
+				8: `		GOPROXY: https://goproxy.cn`,
+				9: `	commands:`,
+				10: `	- go get -u`,
+				11: `	- go build -v`,
+				12: `	- go test -v -race -coverprofile=coverage.txt -covermode=atomic`,
+				13: `	`,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := File(tt.numLines, tt.fileName, []byte(tt.code)); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("File() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}