diff --git a/handler/handler.go b/handler/handler.go
index 4abc598..76c67cb 100644
--- a/handler/handler.go
+++ b/handler/handler.go
@@ -126,7 +126,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 	}
 
 	// Handle LSP methods: transform parameters and send to clangd
-	var uri lsp.DocumentURI
+	var inoURI, cppURI lsp.DocumentURI
 
 	params, err := lsp.ReadParams(req.Method, req.Params)
 	if err != nil {
@@ -146,7 +146,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 
 	case *lsp.DidOpenTextDocumentParams:
 		// method "textDocument/didOpen"
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		log.Printf("--> didOpen(%s@%d as '%s')", p.TextDocument.URI, p.TextDocument.Version, p.TextDocument.LanguageID)
 
 		res, err := handler.didOpen(ctx, p)
@@ -161,7 +161,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 
 	case *lsp.DidChangeTextDocumentParams:
 		// notification "textDocument/didChange"
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		log.Printf("--> didChange(%s@%d)", p.TextDocument.URI, p.TextDocument.Version)
 		for _, change := range p.ContentChanges {
 			log.Printf("     > %s -> %s", change.Range, strconv.Quote(change.Text))
@@ -186,7 +186,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 
 	case *lsp.CompletionParams:
 		// method: "textDocument/completion"
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		log.Printf("--> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character)
 
 		err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams)
@@ -194,7 +194,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 
 	case *lsp.CodeActionParams:
 		// method "textDocument/codeAction"
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		log.Printf("--> codeAction(%s:%s)", p.TextDocument.URI, p.Range.Start)
 
 		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
@@ -202,17 +202,17 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 			break
 		}
 		if p.TextDocument.URI.AsPath().EquivalentTo(handler.buildSketchCpp) {
-			p.Range = handler.sketchMapper.InoToCppLSPRange(uri, p.Range)
+			p.Range = handler.sketchMapper.InoToCppLSPRange(inoURI, p.Range)
 			for index := range p.Context.Diagnostics {
 				r := &p.Context.Diagnostics[index].Range
-				*r = handler.sketchMapper.InoToCppLSPRange(uri, *r)
+				*r = handler.sketchMapper.InoToCppLSPRange(inoURI, *r)
 			}
 		}
 		log.Printf("    --> codeAction(%s:%s)", p.TextDocument.URI, p.Range.Start)
 
 	case *lsp.HoverParams:
 		// method: "textDocument/hover"
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		doc := &p.TextDocumentPositionParams
 		log.Printf("--> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character)
 
@@ -221,16 +221,24 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 
 	case *lsp.DocumentSymbolParams:
 		// method "textDocument/documentSymbol"
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		log.Printf("--> documentSymbol(%s)", p.TextDocument.URI)
 
 		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
 		log.Printf("    --> documentSymbol(%s)", p.TextDocument.URI)
 
+	case *lsp.DocumentFormattingParams:
+		// method "textDocument/formatting"
+		inoURI = p.TextDocument.URI
+		log.Printf("--> formatting(%s)", p.TextDocument.URI)
+		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
+		cppURI = p.TextDocument.URI
+		log.Printf("    --> formatting(%s)", p.TextDocument.URI)
+
 	case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave":
 		log.Printf("--X " + req.Method)
 		return nil, nil
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
 	case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose":
 		log.Printf("--X " + req.Method)
@@ -249,32 +257,27 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 	case *lsp.TextDocumentPositionParams: // "textDocument/documentHighlight":
 		log.Printf("--X " + req.Method)
 		return nil, nil
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		err = handler.ino2cppTextDocumentPositionParams(p)
 	case *lsp.ReferenceParams: // "textDocument/references":
 		log.Printf("--X " + req.Method)
 		return nil, nil
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams)
-	case *lsp.DocumentFormattingParams: // "textDocument/formatting":
-		log.Printf("--X " + req.Method)
-		return nil, nil
-		uri = p.TextDocument.URI
-		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
 	case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting":
 		log.Printf("--X " + req.Method)
 		return nil, nil
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		err = handler.ino2cppDocumentRangeFormattingParams(p)
 	case *lsp.DocumentOnTypeFormattingParams: // "textDocument/onTypeFormatting":
 		log.Printf("--X " + req.Method)
 		return nil, nil
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		err = handler.ino2cppDocumentOnTypeFormattingParams(p)
 	case *lsp.RenameParams: // "textDocument/rename":
 		log.Printf("--X " + req.Method)
 		return nil, nil
-		uri = p.TextDocument.URI
+		inoURI = p.TextDocument.URI
 		err = handler.ino2cppRenameParams(p)
 	case *lsp.DidChangeWatchedFilesParams: // "workspace/didChangeWatchedFiles":
 		log.Printf("--X " + req.Method)
@@ -324,7 +327,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 
 	// Transform and return the result
 	if result != nil {
-		result = handler.transformClangdResult(req.Method, uri, result)
+		result = handler.transformClangdResult(req.Method, inoURI, cppURI, result)
 	}
 	return result, err
 }
@@ -416,7 +419,7 @@ func (handler *InoHandler) refreshCppDocumentSymbols() error {
 	if err != nil {
 		return errors.WithMessage(err, "quering source code symbols")
 	}
-	result = handler.transformClangdResult("textDocument/documentSymbol", cppURI, result)
+	result = handler.transformClangdResult("textDocument/documentSymbol", cppURI, "", result)
 	if symbols, ok := result.([]lsp.DocumentSymbol); !ok {
 		return errors.WithMessage(err, "quering source code symbols (2)")
 	} else {
@@ -702,8 +705,17 @@ func (handler *InoHandler) cpp2inoDocumentURI(cppURI lsp.DocumentURI, cppRange l
 	// Convert build path to sketch path
 	cppPath := cppURI.AsPath()
 	if cppPath.EquivalentTo(handler.buildSketchCpp) {
-		inoPath, inoRange := handler.sketchMapper.CppToInoRange(cppRange)
-		return lsp.NewDocumentURI(inoPath), inoRange, nil
+		inoPath, inoRange, err := handler.sketchMapper.CppToInoRangeOk(cppRange)
+		if err == nil {
+			log.Printf("    URI: converted %s to %s:%s", cppRange, inoPath, inoRange)
+		} else if _, ok := err.(sourcemapper.AdjustedRangeErr); ok {
+			log.Printf("    URI: converted %s to %s:%s (END LINE ADJUSTED)", cppRange, inoPath, inoRange)
+			err = nil
+		} else {
+			log.Printf("    URI: ERROR: %s", err)
+			handler.sketchMapper.DebugLogAll()
+		}
+		return lsp.NewDocumentURI(inoPath), inoRange, err
 	}
 
 	inside, err := cppPath.IsInsideDir(handler.buildSketchRoot)
@@ -817,8 +829,8 @@ func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls
 	return &newEdit
 }
 
-func (handler *InoHandler) transformClangdResult(method string, uri lsp.DocumentURI, result interface{}) interface{} {
-	cppToIno := uri != "" && uri.AsPath().EquivalentTo(handler.buildSketchCpp)
+func (handler *InoHandler) transformClangdResult(method string, inoURI, cppURI lsp.DocumentURI, result interface{}) interface{} {
+	cppToIno := inoURI != "" && inoURI.AsPath().EquivalentTo(handler.buildSketchCpp)
 
 	switch r := result.(type) {
 	case *lsp.Hover:
@@ -853,7 +865,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document
 
 		if r.DocumentSymbolArray != nil {
 			// Treat the input as []DocumentSymbol
-			return handler.cpp2inoDocumentSymbols(*r.DocumentSymbolArray, uri)
+			return handler.cpp2inoDocumentSymbols(*r.DocumentSymbolArray, inoURI)
 		} else if r.SymbolInformationArray != nil {
 			// Treat the input as []SymbolInformation
 			return handler.cpp2inoSymbolInformation(*r.SymbolInformationArray)
@@ -873,11 +885,35 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document
 			}
 			(*r)[i] = lsp.CommandOrCodeAction{
 				Command:    handler.Cpp2InoCommand(item.Command),
-				CodeAction: handler.cpp2inoCodeAction(item.CodeAction, uri),
+				CodeAction: handler.cpp2inoCodeAction(item.CodeAction, inoURI),
 			}
 		}
 		log.Printf("<-- codeAction(%d elements)", len(*r))
 
+	case *[]lsp.TextEdit:
+		// Method: "textDocument/rangeFormatting"
+		// Method: "textDocument/onTypeFormatting"
+		// Method: "textDocument/formatting"
+		log.Printf("    <-- %s %s textEdit(%d elements)", method, cppURI, len(*r))
+		for _, edit := range *r {
+			log.Printf("        > %s -> %s", edit.Range, strconv.Quote(edit.NewText))
+		}
+		sketchEdits, err := handler.cpp2inoTextEdits(cppURI, *r)
+		if err != nil {
+			log.Printf("ERROR converting textEdits: %s", err)
+			return nil
+		}
+
+		inoEdits, ok := sketchEdits[inoURI]
+		if !ok {
+			inoEdits = []lsp.TextEdit{}
+		}
+		log.Printf("<-- %s %s textEdit(%d elements)", method, inoURI, len(inoEdits))
+		for _, edit := range inoEdits {
+			log.Printf("        > %s -> %s", edit.Range, strconv.Quote(edit.NewText))
+		}
+		return &inoEdits
+
 	// case "textDocument/definition":
 	// 	fallthrough
 	// case "textDocument/typeDefinition":
@@ -890,15 +926,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document
 		}
 	case *[]lsp.DocumentHighlight: // "textDocument/documentHighlight":
 		for index := range *r {
-			handler.cpp2inoDocumentHighlight(&(*r)[index], uri)
-		}
-	// case "textDocument/formatting":
-	// 	fallthrough
-	// case "textDocument/rangeFormatting":
-	// 	fallthrough
-	case *[]lsp.TextEdit: // "textDocument/onTypeFormatting":
-		for index := range *r {
-			handler.cpp2inoTextEdit(&(*r)[index], uri)
+			handler.cpp2inoDocumentHighlight(&(*r)[index], inoURI)
 		}
 	case *lsp.WorkspaceEdit: // "textDocument/rename":
 		return handler.cpp2inoWorkspaceEdit(r)
@@ -1013,11 +1041,28 @@ func (handler *InoHandler) cpp2inoDocumentHighlight(highlight *lsp.DocumentHighl
 	// }
 }
 
-func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentURI) {
-	panic("not implemented")
-	// if data, ok := handler.data[uri]; ok {
-	// 	_, edit.Range = data.sourceMap.CppToInoRange(edit.Range)
-	// }
+func (handler *InoHandler) cpp2inoTextEdits(cppURI lsp.DocumentURI, cppEdits []lsp.TextEdit) (map[lsp.DocumentURI][]lsp.TextEdit, error) {
+	res := map[lsp.DocumentURI][]lsp.TextEdit{}
+	for _, cppEdit := range cppEdits {
+		inoURI, inoEdit, err := handler.cpp2inoTextEdit(cppURI, cppEdit)
+		if err != nil {
+			return nil, err
+		}
+		inoEdits, ok := res[inoURI]
+		if !ok {
+			inoEdits = []lsp.TextEdit{}
+		}
+		inoEdits = append(inoEdits, inoEdit)
+		res[inoURI] = inoEdits
+	}
+	return res, nil
+}
+
+func (handler *InoHandler) cpp2inoTextEdit(cppURI lsp.DocumentURI, cppEdit lsp.TextEdit) (lsp.DocumentURI, lsp.TextEdit, error) {
+	inoURI, inoRange, err := handler.cpp2inoDocumentURI(cppURI, cppEdit.Range)
+	inoEdit := cppEdit
+	inoEdit.Range = inoRange
+	return inoURI, inoEdit, err
 }
 
 func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, origURI lsp.DocumentURI) []lsp.DocumentSymbol {
diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go
index a921b40..3a05f07 100644
--- a/handler/sourcemapper/ino.go
+++ b/handler/sourcemapper/ino.go
@@ -4,12 +4,14 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
+	"log"
 	"sort"
 	"strconv"
 	"strings"
 
 	"github.com/bcmi-labs/arduino-language-server/handler/textutils"
 	"github.com/bcmi-labs/arduino-language-server/lsp"
+	"github.com/pkg/errors"
 )
 
 // InoMapper is a mapping between the .ino sketch and the preprocessed .cpp file
@@ -78,17 +80,50 @@ func (s *InoMapper) CppToInoLine(targetLine int) (string, int) {
 	return res.File, res.Line
 }
 
-// CppToInoRange converts a target (.cpp) lsp.Range into a source.ino:lsp.Range
-func (s *InoMapper) CppToInoRange(r lsp.Range) (string, lsp.Range) {
-	startFile, startLine := s.CppToInoLine(r.Start.Line)
-	endFile, endLine := s.CppToInoLine(r.End.Line)
-	res := r
-	res.Start.Line = startLine
-	res.End.Line = endLine
-	if startFile != endFile {
-		panic("invalid range conversion")
+// CppToInoRange converts a target (.cpp) lsp.Range into a source.ino:lsp.Range.
+// It will panic if the range spans across multiple ino files.
+func (s *InoMapper) CppToInoRange(cppRange lsp.Range) (string, lsp.Range) {
+	inoFile, inoRange, err := s.CppToInoRangeOk(cppRange)
+	if err != nil {
+		panic(err.Error())
 	}
-	return startFile, res
+	return inoFile, inoRange
+}
+
+// AdjustedRangeErr is returned if the range overlaps with a non-ino section by just the
+// last newline character.
+type AdjustedRangeErr struct{}
+
+func (e AdjustedRangeErr) Error() string {
+	return "the range has been adjusted to allow final newline"
+}
+
+// CppToInoRangeOk converts a target (.cpp) lsp.Range into a source.ino:lsp.Range.
+// It returns an error if the range spans across multiple ino files.
+// If the range ends on the beginning of a new line in another .ino file, the range
+// is adjusted and AdjustedRangeErr is reported as err: the range may be still valid.
+func (s *InoMapper) CppToInoRangeOk(cppRange lsp.Range) (string, lsp.Range, error) {
+	inoFile, startLine := s.CppToInoLine(cppRange.Start.Line)
+	endInoFile, endLine := s.CppToInoLine(cppRange.End.Line)
+	inoRange := cppRange
+	inoRange.Start.Line = startLine
+	inoRange.End.Line = endLine
+	if inoFile == endInoFile {
+		// All done
+		return inoFile, inoRange, nil
+	}
+
+	// Special case: the last line ends up in the "not-ino" area
+	if inoRange.End.Character == 0 {
+		if checkFile, checkLine := s.CppToInoLine(cppRange.End.Line - 1); checkFile == inoFile {
+			// Adjust the range and return it with an AdjustedRange notification
+			inoRange.End.Line = checkLine + 1
+			return inoFile, inoRange, AdjustedRangeErr{}
+		}
+	}
+
+	// otherwise the range is not recoverable, just report error
+	return inoFile, inoRange, errors.Errorf("invalid range conversion %s -> %s:%d-%s:%d", cppRange, inoFile, startLine, endInoFile, endLine)
 }
 
 // CppToInoLineOk converts a target (.cpp) line into a source (.ino) line and
@@ -319,3 +354,13 @@ func dumpInoToCppMap(s map[InoLine]int) {
 		fmt.Printf("%s:%d -> %d\n", inoLine.File, inoLine.Line, cppLine)
 	}
 }
+
+// DebugLogAll dumps the internal status of the mapper
+func (s *InoMapper) DebugLogAll() {
+	cpp := strings.Split(s.CppText.Text, "\n")
+	log.Printf("  > Current sketchmapper content:")
+	for l, cppLine := range cpp {
+		inoFile, inoLine := s.CppToInoLine(l)
+		log.Printf("  %3d: %-40s : %s:%d", l, cppLine, inoFile, inoLine)
+	}
+}