package main import ( "fmt" "os" "strings" ) type ReadRangeResult struct { Content string `json:"content"` Lines int `json:"lines"` Bytes int `json:"bytes"` } type WriteRangeResult struct { Success bool `json:"success"` LinesWritten int `json:"lines_written"` BytesWritten int `json:"bytes_written"` Message string `json:"message,omitempty"` } // Helper function to convert line/column positions to byte offsets func lineColToByteRange(data []byte, startLine, endLine, startCol, endCol int) (startByte, endByte int, err error) { if len(data) == 0 { return 0, 0, nil } // Convert to 0-based indexing if startLine > 0 { startLine-- } if endLine > 0 { endLine-- } // Only decrement columns if they are positive (not -1 which means unspecified) if startCol > 0 { startCol-- } if endCol > 0 { endCol-- } currentLine := 0 currentCol := 0 startByte = -1 endByte = -1 for i := 0; i < len(data); i++ { // Check if we're at the start position if currentLine == startLine && startByte == -1 { if startCol < 0 || currentCol == startCol { startByte = i } } // Check if we're at the end position if currentLine == endLine { if endCol < 0 { // No end column specified, go to end of line for j := i; j < len(data) && data[j] != '\n'; j++ { i = j } endByte = i + 1 if endByte > len(data) { endByte = len(data) } break } else if currentCol == endCol { endByte = i break } } // Move to next character if data[i] == '\n' { // End of line reached if currentLine == endLine && endByte == -1 { endByte = i break } currentLine++ currentCol = 0 } else { currentCol++ } // If we've passed the end line, set end byte if currentLine > endLine && endByte == -1 { endByte = i break } } // Handle end of file cases if startByte == -1 { return 0, 0, fmt.Errorf("start position (line %d, col %d) not found", startLine+1, startCol+1) } if endByte == -1 { endByte = len(data) } return startByte, endByte, nil } func readRange(file string, startLine, endLine, startCol, endCol, startByte, endByte int) (*ReadRangeResult, error) { data, err := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) } // Convert line/column to byte range if needed if startByte < 0 || endByte < 0 { startByte, endByte, err = lineColToByteRange(data, startLine, endLine, startCol, endCol) if err != nil { return nil, err } } // Validate byte range if startByte < 0 || startByte > len(data) { return nil, fmt.Errorf("start byte %d out of range (file size: %d)", startByte, len(data)) } if endByte < startByte { return nil, fmt.Errorf("end byte %d is before start byte %d", endByte, startByte) } if endByte > len(data) { endByte = len(data) } // Extract content content := string(data[startByte:endByte]) return &ReadRangeResult{ Content: content, Lines: strings.Count(content, "\n") + 1, Bytes: len(content), }, nil } func writeRange(file string, content string, startLine, endLine, startCol, endCol, startByte, endByte int, confirmOld string) (*WriteRangeResult, error) { data, err := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) } // Convert line/column to byte range if needed if startByte < 0 || endByte < 0 { startByte, endByte, err = lineColToByteRange(data, startLine, endLine, startCol, endCol) if err != nil { return nil, err } } // Validate byte range if startByte < 0 || startByte > len(data) { return nil, fmt.Errorf("start byte %d out of range (file size: %d)", startByte, len(data)) } if endByte < startByte { return nil, fmt.Errorf("end byte %d is before start byte %d", endByte, startByte) } if endByte > len(data) { endByte = len(data) } // Extract old content oldContent := string(data[startByte:endByte]) // Check confirmation if provided if confirmOld != "" && oldContent != confirmOld { return &WriteRangeResult{ Success: false, Message: fmt.Sprintf("content mismatch: expected %q but found %q", confirmOld, oldContent), }, nil } // Build new content newData := make([]byte, 0, len(data)-len(oldContent)+len(content)) newData = append(newData, data[:startByte]...) newData = append(newData, []byte(content)...) newData = append(newData, data[endByte:]...) // Write the file err = os.WriteFile(file, newData, 0644) if err != nil { return nil, fmt.Errorf("failed to write file: %w", err) } return &WriteRangeResult{ Success: true, LinesWritten: strings.Count(content, "\n") + 1, BytesWritten: len(content), Message: "Successfully written", }, nil }