func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, rng span.Range, node ast.Node) ([]*variable, error) {
	id := func(n *ast.Ident) (types.Object, bool) {
		obj := info.Uses[n]
		if obj == nil {
			return info.Defs[n], false
		}
		if obj.Name() == "_" {
			return nil, false  
		}
		if _, ok := obj.(*types.PkgName); ok {
			return nil, false  
		}
		if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
			return nil, false  
		}
		scope := obj.Parent()
		if scope == nil {
			return nil, false  
		}
		if scope == fileScope || scope == pkgScope {
			return nil, false  
		}
		if rng.Start <= obj.Pos() && obj.Pos() <= rng.End {
			return obj, false  
		}
		return obj, true
	}
	var sel func(n *ast.SelectorExpr) (types.Object, bool)
	sel = func(n *ast.SelectorExpr) (types.Object, bool) {
		switch x := astutil.Unparen(n.X).(type) {
		case *ast.SelectorExpr:
			return sel(x)
		case *ast.Ident:
			return id(x)
		}
		return nil, false
	}
	seen := make(map[types.Object]*variable)
	firstUseIn := make(map[types.Object]token.Pos)
	var vars []types.Object
	ast.Inspect(node, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		if rng.Start <= n.Pos() && n.End() <= rng.End {
			var obj types.Object
			var isFree, prune bool
			switch n := n.(type) {
			case *ast.Ident:
				obj, isFree = id(n)
			case *ast.SelectorExpr:
				obj, isFree = sel(n)
				prune = true
			}
			if obj != nil {
				seen[obj] = &variable{
					obj:  obj,
					free: isFree,
				}
				vars = append(vars, obj)
				first, ok := firstUseIn[obj]
				if !ok || n.Pos() < first {
					firstUseIn[obj] = n.Pos()
				}
				if prune {
					return false
				}
			}
		}
		return n.Pos() <= rng.End
	})
	ast.Inspect(node, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		if n.Pos() < rng.Start || n.End() > rng.End {
			return n.Pos() <= rng.End
		}
		switch n := n.(type) {
		case *ast.AssignStmt:
			for _, assignment := range n.Lhs {
				lhs, ok := assignment.(*ast.Ident)
				if !ok {
					continue
				}
				obj, _ := id(lhs)
				if obj == nil {
					continue
				}
				if _, ok := seen[obj]; !ok {
					continue
				}
				seen[obj].assigned = true
				if n.Tok != token.DEFINE {
					continue
				}
				if firstUseIn[obj] != lhs.Pos() {
					continue
				}
				for _, expr := range n.Rhs {
					if referencesObj(info, expr, obj) {
						continue
					}
					if _, ok := seen[obj]; !ok {
						continue
					}
					seen[obj].defined = true
					break
				}
			}
			return false
		case *ast.DeclStmt:
			gen, ok := n.Decl.(*ast.GenDecl)
			if !ok {
				return false
			}
			for _, spec := range gen.Specs {
				vSpecs, ok := spec.(*ast.ValueSpec)
				if !ok {
					continue
				}
				for _, vSpec := range vSpecs.Names {
					obj, _ := id(vSpec)
					if obj == nil {
						continue
					}
					if _, ok := seen[obj]; !ok {
						continue
					}
					seen[obj].assigned = true
				}
			}
			return false
		case *ast.IncDecStmt:
			if ident, ok := n.X.(*ast.Ident); !ok {
				return false
			} else if obj, _ := id(ident); obj == nil {
				return false
			} else {
				if _, ok := seen[obj]; !ok {
					return false
				}
				seen[obj].assigned = true
			}
		}
		return true
	})
	var variables []*variable
	for _, obj := range vars {
		v, ok := seen[obj]
		if !ok {
			return nil, fmt.Errorf("no seen types.Object for %v", obj)
		}
		variables = append(variables, v)
	}
	return variables, nil
}
collectFreeVars maps each identifier in the given range to whether it is "free."
Given a range, a variable in that range is defined as "free" if it is declared
outside of the range and neither at the file scope nor package scope. These free
variables will be used as arguments in the extracted function. It also returns a
list of identifiers that may need to be returned by the extracted function.
Some of the code in this function has been adapted from tools/cmd/guru/freevars.go.
package source
import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"go/types"
	"strings"
	"unicode"
	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/ast/astutil"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/analysisinternal"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/span"
)
func extractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
	expr, path, ok, err := CanExtractVariable(rng, file)
	if !ok {
		return nil, fmt.Errorf("extractVariable: cannot extract %s: %v", fset.Position(rng.Start), err)
	}
	var lhsNames []string
	switch expr := expr.(type) {
	case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr,
		*ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
		lhsName, _ := generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0)
		lhsNames = append(lhsNames, lhsName)
	case *ast.CallExpr:
		tup, ok := info.TypeOf(expr).(*types.Tuple)
		if !ok {
			lhsName, _ := generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0)
			lhsNames = append(lhsNames, lhsName)
			break
		}
		idx := 0
		for i := 0; i < tup.Len(); i++ {
			var lhsName string
			lhsName, idx = generateAvailableIdentifier(expr.Pos(), file, path, info, "x", idx)
			lhsNames = append(lhsNames, lhsName)
		}
	default:
		return nil, fmt.Errorf("cannot extract %T", expr)
	}
	insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
	if insertBeforeStmt == nil {
		return nil, fmt.Errorf("cannot find location to insert extraction")
	}
	tok := fset.File(expr.Pos())
	if tok == nil {
		return nil, fmt.Errorf("no file for pos %v", fset.Position(file.Pos()))
	}
	indent, err := calculateIndentation(src, tok, insertBeforeStmt)
	if err != nil {
		return nil, err
	}
	newLineIndent := "\n" + indent
	lhs := strings.Join(lhsNames, ", ")
	assignStmt := &ast.AssignStmt{
		Lhs: []ast.Expr{ast.NewIdent(lhs)},
		Tok: token.DEFINE,
		Rhs: []ast.Expr{expr},
	}
	var buf bytes.Buffer
	if err := format.Node(&buf, fset, assignStmt); err != nil {
		return nil, err
	}
	assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent
	return &analysis.SuggestedFix{
		TextEdits: []analysis.TextEdit{
			{
				Pos:     insertBeforeStmt.Pos(),
				End:     insertBeforeStmt.Pos(),
				NewText: []byte(assignment),
			},
			{
				Pos:     rng.Start,
				End:     rng.End,
				NewText: []byte(lhs),
			},
		},
	}, nil
}
func CanExtractVariable(rng span.Range, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
	if rng.Start == rng.End {
		return nil, nil, false, fmt.Errorf("start and end are equal")
	}
	path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
	if len(path) == 0 {
		return nil, nil, false, fmt.Errorf("no path enclosing interval")
	}
	for _, n := range path {
		if _, ok := n.(*ast.ImportSpec); ok {
			return nil, nil, false, fmt.Errorf("cannot extract variable in an import block")
		}
	}
	node := path[0]
	if rng.Start != node.Pos() || rng.End != node.End() {
		return nil, nil, false, fmt.Errorf("range does not map to an AST node")
	}
	expr, ok := node.(ast.Expr)
	if !ok {
		return nil, nil, false, fmt.Errorf("node is not an expression")
	}
	switch expr.(type) {
	case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.CallExpr,
		*ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
		return expr, path, true, nil
	}
	return nil, nil, false, fmt.Errorf("cannot extract an %T to a variable", expr)
}
func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.Node) (string, error) {
	line := tok.Line(insertBeforeStmt.Pos())
	lineOffset, err := Offset(tok, tok.LineStart(line))
	if err != nil {
		return "", err
	}
	stmtOffset, err := Offset(tok, insertBeforeStmt.Pos())
	if err != nil {
		return "", err
	}
	return string(content[lineOffset:stmtOffset]), nil
}
func generateAvailableIdentifier(pos token.Pos, file *ast.File, path []ast.Node, info *types.Info, prefix string, idx int) (string, int) {
	scopes := CollectScopes(info, path, pos)
	return generateIdentifier(idx, prefix, func(name string) bool {
		return file.Scope.Lookup(name) != nil || !isValidName(name, scopes)
	})
}
func generateIdentifier(idx int, prefix string, hasCollision func(string) bool) (string, int) {
	name := prefix
	if idx != 0 {
		name += fmt.Sprintf("%d", idx)
	}
	for hasCollision(name) {
		idx++
		name = fmt.Sprintf("%v%d", prefix, idx)
	}
	return name, idx + 1
}
func isValidName(name string, scopes []*types.Scope) bool {
	for _, scope := range scopes {
		if scope == nil {
			continue
		}
		if scope.Lookup(name) != nil {
			return false
		}
	}
	return true
}
type returnVariable struct {
	name ast.Expr
	decl *ast.Field
	zeroVal ast.Expr
}
func extractMethod(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
	return extractFunctionMethod(fset, rng, src, file, pkg, info, true)
}
func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
	return extractFunctionMethod(fset, rng, src, file, pkg, info, false)
}
func extractFunctionMethod(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*analysis.SuggestedFix, error) {
	errorPrefix := "extractFunction"
	if isMethod {
		errorPrefix = "extractMethod"
	}
	p, ok, methodOk, err := CanExtractFunction(fset, rng, src, file)
	if (!ok && !isMethod) || (!methodOk && isMethod) {
		return nil, fmt.Errorf("%s: cannot extract %s: %v", errorPrefix,
			fset.Position(rng.Start), err)
	}
	tok, path, rng, outer, start := p.tok, p.path, p.rng, p.outer, p.start
	fileScope := info.Scopes[file]
	if fileScope == nil {
		return nil, fmt.Errorf("%s: file scope is empty", errorPrefix)
	}
	pkgScope := fileScope.Parent()
	if pkgScope == nil {
		return nil, fmt.Errorf("%s: package scope is empty", errorPrefix)
	}
	var retStmts []*ast.ReturnStmt
	var hasNonNestedReturn bool
	startParent := findParent(outer, start)
	ast.Inspect(outer, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		if n.Pos() < rng.Start || n.End() > rng.End {
			return n.Pos() <= rng.End
		}
		ret, ok := n.(*ast.ReturnStmt)
		if !ok {
			return true
		}
		if findParent(outer, n) == startParent {
			hasNonNestedReturn = true
		}
		retStmts = append(retStmts, ret)
		return false
	})
	containsReturnStatement := len(retStmts) > 0
	variables, err := collectFreeVars(info, file, fileScope, pkgScope, rng, path[0])
	if err != nil {
		return nil, err
	}
	var (
		receiverUsed bool
		receiver     *ast.Field
		receiverName string
		receiverObj  types.Object
	)
	if isMethod {
		if outer == nil || outer.Recv == nil || len(outer.Recv.List) == 0 {
			return nil, fmt.Errorf("%s: cannot extract need method receiver", errorPrefix)
		}
		receiver = outer.Recv.List[0]
		if len(receiver.Names) == 0 || receiver.Names[0] == nil {
			return nil, fmt.Errorf("%s: cannot extract need method receiver name", errorPrefix)
		}
		recvName := receiver.Names[0]
		receiverName = recvName.Name
		receiverObj = info.ObjectOf(recvName)
	}
	var (
		params, returns         []ast.Expr      
		paramTypes, returnTypes []*ast.Field    
		uninitialized           []types.Object  
	)
	seenVars := make(map[types.Object]ast.Expr)
	seenUninitialized := make(map[types.Object]struct{})
	var canRedefineCount int
	for _, v := range variables {
		if _, ok := seenVars[v.obj]; ok {
			continue
		}
		if v.obj.Name() == "_" {
			continue
		}
		typ := analysisinternal.TypeExpr(fset, file, pkg, v.obj.Type())
		if typ == nil {
			return nil, fmt.Errorf("nil AST expression for type: %v", v.obj.Name())
		}
		seenVars[v.obj] = typ
		identifier := ast.NewIdent(v.obj.Name())
		if v.obj.Parent() == nil {
			return nil, fmt.Errorf("parent nil")
		}
		isUsed, firstUseAfter := objUsed(info, span.NewRange(fset, rng.End, v.obj.Parent().End()), v.obj)
		if v.assigned && isUsed && !varOverridden(info, firstUseAfter, v.obj, v.free, outer) {
			returnTypes = append(returnTypes, &ast.Field{Type: typ})
			returns = append(returns, identifier)
			if !v.free {
				uninitialized = append(uninitialized, v.obj)
			} else if v.obj.Parent().Pos() == startParent.Pos() {
				canRedefineCount++
			}
		}
		if v.free && !v.defined {
			if isMethod && v.obj == receiverObj {
				receiverUsed = true
				continue
			}
			params = append(params, identifier)
			paramTypes = append(paramTypes, &ast.Field{
				Names: []*ast.Ident{identifier},
				Type:  typ,
			})
		}
	}
	enclosing := outer.Type
	for _, p := range path {
		if p == enclosing {
			break
		}
		if fl, ok := p.(*ast.FuncLit); ok {
			enclosing = fl.Type
			break
		}
	}
	startOffset, err := Offset(tok, rng.Start)
	if err != nil {
		return nil, err
	}
	endOffset, err := Offset(tok, rng.End)
	if err != nil {
		return nil, err
	}
	selection := src[startOffset:endOffset]
	extractedBlock, err := parseBlockStmt(fset, selection)
	if err != nil {
		return nil, err
	}
	var retVars []*returnVariable
	var ifReturn *ast.IfStmt
	if containsReturnStatement {
		if !hasNonNestedReturn {
			if err := adjustReturnStatements(returnTypes, seenVars, fset, file,
				pkg, extractedBlock); err != nil {
				return nil, err
			}
		}
		retVars, ifReturn, err = generateReturnInfo(enclosing, pkg, path, file, info, fset, rng.Start, hasNonNestedReturn)
		if err != nil {
			return nil, err
		}
	}
	hasReturnValues := len(returns)+len(retVars) > 0
	if hasReturnValues && !hasNonNestedReturn {
		extractedBlock.List = append(extractedBlock.List, &ast.ReturnStmt{
			Results: append(returns, getZeroVals(retVars)...),
		})
	}
	sym := token.ASSIGN
	canDefineCount := len(uninitialized) + canRedefineCount
	canDefine := len(uninitialized)+len(retVars) > 0 && canDefineCount == len(returns)
	if canDefine {
		sym = token.DEFINE
	}
	var name, funName string
	if isMethod {
		name = "newMethod"
		funName = name
	} else {
		name = "newFunction"
		funName, _ = generateAvailableIdentifier(rng.Start, file, path, info, name, 0)
	}
	extractedFunCall := generateFuncCall(hasNonNestedReturn, hasReturnValues, params,
		append(returns, getNames(retVars)...), funName, sym, receiverName)
	newFunc := &ast.FuncDecl{
		Name: ast.NewIdent(funName),
		Type: &ast.FuncType{
			Params:  &ast.FieldList{List: paramTypes},
			Results: &ast.FieldList{List: append(returnTypes, getDecls(retVars)...)},
		},
		Body: extractedBlock,
	}
	if isMethod {
		var names []*ast.Ident
		if receiverUsed {
			names = append(names, ast.NewIdent(receiverName))
		}
		newFunc.Recv = &ast.FieldList{
			List: []*ast.Field{{
				Names: names,
				Type:  receiver.Type,
			}},
		}
	}
	var declarations []ast.Stmt
	if canDefineCount != len(returns) {
		declarations = initializeVars(uninitialized, retVars, seenUninitialized, seenVars)
	}
	var declBuf, replaceBuf, newFuncBuf, ifBuf, commentBuf bytes.Buffer
	if err := format.Node(&declBuf, fset, declarations); err != nil {
		return nil, err
	}
	if err := format.Node(&replaceBuf, fset, extractedFunCall); err != nil {
		return nil, err
	}
	if ifReturn != nil {
		if err := format.Node(&ifBuf, fset, ifReturn); err != nil {
			return nil, err
		}
	}
	if err := format.Node(&newFuncBuf, fset, newFunc); err != nil {
		return nil, err
	}
	for _, cg := range file.Comments {
		if cg.Pos().IsValid() && cg.Pos() < rng.End && cg.Pos() >= rng.Start {
			for _, c := range cg.List {
				fmt.Fprintln(&commentBuf, c.Text)
			}
		}
	}
	outerStart, err := Offset(tok, outer.Pos())
	if err != nil {
		return nil, err
	}
	outerEnd, err := Offset(tok, outer.End())
	if err != nil {
		return nil, err
	}
	before := src[outerStart:startOffset]
	after := src[endOffset:outerEnd]
	indent, err := calculateIndentation(src, tok, start)
	if err != nil {
		return nil, err
	}
	newLineIndent := "\n" + indent
	var fullReplacement strings.Builder
	fullReplacement.Write(before)
	if commentBuf.Len() > 0 {
		comments := strings.ReplaceAll(commentBuf.String(), "\n", newLineIndent)
		fullReplacement.WriteString(comments)
	}
	if declBuf.Len() > 0 {  
		initializations := strings.ReplaceAll(declBuf.String(), "\n", newLineIndent) +
			newLineIndent
		fullReplacement.WriteString(initializations)
	}
	fullReplacement.Write(replaceBuf.Bytes())  
	if ifBuf.Len() > 0 {                       
		ifstatement := newLineIndent +
			strings.ReplaceAll(ifBuf.String(), "\n", newLineIndent)
		fullReplacement.WriteString(ifstatement)
	}
	fullReplacement.Write(after)
	fullReplacement.WriteString("\n\n")        
	fullReplacement.Write(newFuncBuf.Bytes())  
	return &analysis.SuggestedFix{
		TextEdits: []analysis.TextEdit{{
			Pos:     outer.Pos(),
			End:     outer.End(),
			NewText: []byte(fullReplacement.String()),
		}},
	}, nil
}
func adjustRangeForWhitespace(rng span.Range, tok *token.File, content []byte) (span.Range, error) {
	offset, err := Offset(tok, rng.Start)
	if err != nil {
		return span.Range{}, err
	}
	for offset < len(content) {
		if !unicode.IsSpace(rune(content[offset])) {
			break
		}
		offset += 1
	}
	rng.Start = tok.Pos(offset)
	offset, err = Offset(tok, rng.End)
	if err != nil {
		return span.Range{}, err
	}
	for o := offset - 1; 0 <= o && o < len(content); o-- {
		if !unicode.IsSpace(rune(content[o])) {
			break
		}
		offset = o
	}
	rng.End = tok.Pos(offset)
	return rng, nil
}
func findParent(start ast.Node, target ast.Node) ast.Node {
	var parent ast.Node
	analysisinternal.WalkASTWithParent(start, func(n, p ast.Node) bool {
		if n == target {
			parent = p
			return false
		}
		return true
	})
	return parent
}
type variable struct {
	obj types.Object
	free bool
	assigned bool
	defined bool
}