internal/lsp/source: optimize computeFixEdits

In the common case that a file has imports, we're going to diff just the
import block. That means that ApplyFixes doesn't need to produce the
whole formatted file, which is a huge speedup. We will do more work twice
for files with no imports, but those are presumably pretty short.

Hat tip to Muir for pointing towards this in
https://go-review.googlesource.com/c/tools/+/209579/2/internal/imports/imports.go#87
even if I didn't catch it.

Updates golang/go#36001.

Change-Id: Ibbeb4d88c6505eac26a36994de514813606c8c79
Reviewed-on: https://go-review.googlesource.com/c/tools/+/210200
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/imports/imports.go b/internal/imports/imports.go
index 7c1b475..e066d90 100644
--- a/internal/imports/imports.go
+++ b/internal/imports/imports.go
@@ -83,8 +83,9 @@
 	return getFixes(fileSet, file, filename, opt.Env)
 }
 
-// ApplyFix will apply all of the fixes to the file and format it.
-func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options) (formatted []byte, err error) {
+// ApplyFixes applies all of the fixes to the file and formats it. extraMode
+// is added in when parsing the file.
+func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) {
 	src, opt, err = initialize(filename, src, opt)
 	if err != nil {
 		return nil, err
@@ -100,6 +101,8 @@
 	if opt.AllErrors {
 		parserMode |= parser.AllErrors
 	}
+	parserMode |= extraMode
+
 	file, err := parser.ParseFile(fileSet, filename, src, parserMode)
 	if file == nil {
 		return nil, err
diff --git a/internal/imports/imports_test.go b/internal/imports/imports_test.go
index 2288873..1901e6a 100644
--- a/internal/imports/imports_test.go
+++ b/internal/imports/imports_test.go
@@ -71,7 +71,7 @@
 			t.Errorf("%s: %s", test.name, err.Error())
 		}
 
-		got, err = ApplyFixes(fixes, "", []byte(input), test.opt)
+		got, err = ApplyFixes(fixes, "", []byte(input), test.opt, 0)
 		if err != nil {
 			t.Errorf("%s: %s", test.name, err.Error())
 		}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 0e49f9a..35b7981 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -93,7 +93,7 @@
 
 	pkg, pgh, err := getParsedFile(ctx, snapshot, f, NarrowestCheckPackageHandle)
 	if err != nil {
-		return nil, nil, fmt.Errorf("getting file for AllImportsFixes: %v", err)
+		return nil, nil, errors.Errorf("getting file for AllImportsFixes: %v", err)
 	}
 	if hasListErrors(pkg) {
 		return nil, nil, errors.Errorf("%s has list errors, not running goimports", f.URI())
@@ -112,7 +112,7 @@
 		return err
 	}, options)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, errors.Errorf("computing fix edits: %v", err)
 	}
 
 	return allFixEdits, editsPerFix, nil
@@ -186,7 +186,8 @@
 	filename := ph.File().Identity().URI.Filename()
 	// Apply the fixes and re-parse the file so that we can locate the
 	// new imports.
-	fixedData, err := imports.ApplyFixes(fixes, filename, origData, options)
+	fixedData, err := imports.ApplyFixes(fixes, filename, origData, options, parser.ImportsOnly)
+	fixedData = append(fixedData, '\n') // ApplyFixes comes out missing the newline, go figure.
 	if err != nil {
 		return nil, err
 	}
@@ -211,7 +212,11 @@
 	// somewhere before that.
 	if origImportOffset == 0 || fixedImportsOffset == 0 {
 		left, _ = trimToFirstNonImport(view.Session().Cache().FileSet(), origAST, origData, nil)
-		// We need the whole AST here, not just the ImportsOnly AST we parsed above.
+		fixedData, err = imports.ApplyFixes(fixes, filename, origData, options, 0)
+		if err != nil {
+			return nil, err
+		}
+		// We need the whole file here, not just the ImportsOnly versions we made above.
 		fixedAST, err = parser.ParseFile(fixedFset, filename, fixedData, 0)
 		if fixedAST == nil {
 			return nil, err