goprotobuf: sync from Google internal version.

Interesting changes:
  - String method on protocol buffer message types
    that renders the message in compact text format.
  - Extension text formatting.

R=r
CC=golang-dev
http://codereview.appspot.com/4643050
diff --git a/compiler/generator/generator.go b/compiler/generator/generator.go
index 94d3768..884fef1 100644
--- a/compiler/generator/generator.go
+++ b/compiler/generator/generator.go
@@ -195,6 +195,17 @@
 	return s
 }
 
+// DescName returns the variable name used for the generated descriptor.
+func (e *ExtensionDescriptor) DescName() string {
+	// The full type name.
+	typeName := e.TypeName()
+	// Each scope of the extension is individually CamelCased, and all are joined with "_" with an "E_" prefix.
+	for i, s := range typeName {
+		typeName[i] = CamelCase(s)
+	}
+	return "E_" + strings.Join(typeName, "_")
+}
+
 // FileDescriptor describes an protocol buffer descriptor file (.proto).
 // It includes slices of all the messages and enums defined within it.
 // Those slices are constructed by WrapTypes.
@@ -244,6 +255,7 @@
 
 	g.P("type ", ms.sym, " ", remoteSym)
 	g.P("func (this *", ms.sym, ") Reset() { (*", remoteSym, ")(this).Reset() }")
+	g.P("func (this *", ms.sym, ") String() string { return (*", remoteSym, ")(this).String() }")
 	if ms.hasExtensions {
 		g.P("func (*", ms.sym, ") ExtensionRangeArray() []", g.ProtoPkg, ".ExtensionRange ",
 			"{ return (*", remoteSym, ")(nil).ExtensionRangeArray() }")
@@ -892,7 +904,8 @@
 	if field.Options != nil && proto.GetBool(field.Options.Packed) {
 		packed = ",packed"
 	}
-	name := proto.GetString(field.Name)
+	fieldName := proto.GetString(field.Name)
+	name := fieldName
 	if *field.Type == descriptor.FieldDescriptorProto_TYPE_GROUP {
 		// We must use the type name for groups instead of
 		// the field name to preserve capitalization.
@@ -902,8 +915,8 @@
 		if i := strings.LastIndex(name, "."); i >= 0 {
 			name = name[i+1:]
 		}
-		name = ",name=" + name
-	} else if name == CamelCase(name) {
+	}
+	if name == CamelCase(fieldName) {
 		name = ""
 	} else {
 		name = ",name=" + name
@@ -1028,12 +1041,9 @@
 	g.Out()
 	g.P("}")
 
-	// Reset function
-	g.P("func (this *", ccTypeName, ") Reset() {")
-	g.In()
-	g.P("*this = ", ccTypeName, "{}")
-	g.Out()
-	g.P("}")
+	// Reset and String functions
+	g.P("func (this *", ccTypeName, ") Reset() { *this = ", ccTypeName, "{} }")
+	g.P("func (this *", ccTypeName, ") String() string { return ", g.ProtoPkg, ".CompactTextString(this) }")
 
 	// Extension support methods
 	var hasExtensions, isMessageSet bool
@@ -1140,13 +1150,7 @@
 }
 
 func (g *Generator) generateExtension(ext *ExtensionDescriptor) {
-	// The full type name
-	typeName := ext.TypeName()
-	// Each scope of the extension is individually CamelCased, and all are joined with "_" with an "E_" prefix.
-	for i, s := range typeName {
-		typeName[i] = CamelCase(s)
-	}
-	ccTypeName := "E_" + strings.Join(typeName, "_")
+	ccTypeName := ext.DescName()
 
 	extendedType := "*" + g.TypeName(g.ObjectNamed(*ext.Extendee))
 	field := ext.FieldDescriptorProto
@@ -1163,6 +1167,7 @@
 	g.P("ExtendedType: (", extendedType, ")(nil),")
 	g.P("ExtensionType: (", fieldType, ")(nil),")
 	g.P("Field: ", field.Number, ",")
+	g.P(`Name: "`, g.packageName, ".", *field.Name, `",`)
 	g.P("Tag: ", tag, ",")
 
 	g.Out()
@@ -1178,6 +1183,14 @@
 	for _, enum := range g.file.enum {
 		g.generateEnumRegistration(enum)
 	}
+	for _, d := range g.file.desc {
+		for _, ext := range d.ext {
+			g.generateExtensionRegistration(ext)
+		}
+	}
+	for _, ext := range g.file.ext {
+		g.generateExtensionRegistration(ext)
+	}
 	g.Out()
 	g.P("}")
 }
@@ -1191,6 +1204,10 @@
 	g.P(g.ProtoPkg+".RegisterEnum(", Quote(pkg+ccTypeName), ", ", ccTypeName+"_name, ", ccTypeName+"_value)")
 }
 
+func (g *Generator) generateExtensionRegistration(ext *ExtensionDescriptor) {
+	g.P(g.ProtoPkg+".RegisterExtension(", ext.DescName(), ")")
+}
+
 // And now lots of helper functions.
 
 // Is c an ASCII lower-case letter?
diff --git a/compiler/testdata/Makefile b/compiler/testdata/Makefile
index 8238870..f1dcd50 100644
--- a/compiler/testdata/Makefile
+++ b/compiler/testdata/Makefile
@@ -45,7 +45,7 @@
 
 golden:
 	$(QUOTED_GOBIN)/gomake -B test.pb.go
-	diff test.pb.go test.pb.go.golden
+	diff -w test.pb.go test.pb.go.golden
 
 nuke:	clean
 
diff --git a/compiler/testdata/test.pb.go.golden b/compiler/testdata/test.pb.go.golden
index ccd0f09..50b2f70 100644
--- a/compiler/testdata/test.pb.go.golden
+++ b/compiler/testdata/test.pb.go.golden
@@ -17,17 +17,17 @@
 type HatType int32
 
 const (
-	HatType_FEDORA	= 1
-	HatType_FEZ	= 2
+	HatType_FEDORA = 1
+	HatType_FEZ    = 2
 )
 
 var HatType_name = map[int32]string{
-	1:	"FEDORA",
-	2:	"FEZ",
+	1: "FEDORA",
+	2: "FEZ",
 }
 var HatType_value = map[string]int32{
-	"FEDORA":	1,
-	"FEZ":		2,
+	"FEDORA": 1,
+	"FEZ":    2,
 }
 
 func NewHatType(x int32) *HatType {
@@ -41,20 +41,20 @@
 type Days int32
 
 const (
-	Days_MONDAY	= 1
-	Days_TUESDAY	= 2
-	Days_LUNDI	= 1
+	Days_MONDAY  = 1
+	Days_TUESDAY = 2
+	Days_LUNDI   = 1
 )
 
 var Days_name = map[int32]string{
-	1:	"MONDAY",
-	2:	"TUESDAY",
+	1: "MONDAY",
+	2: "TUESDAY",
 	// Duplicate value: 1: "LUNDI",
 }
 var Days_value = map[string]int32{
-	"MONDAY":	1,
-	"TUESDAY":	2,
-	"LUNDI":	1,
+	"MONDAY":  1,
+	"TUESDAY": 2,
+	"LUNDI":   1,
 }
 
 func NewDays(x int32) *Days {
@@ -68,20 +68,20 @@
 type Request_Color int32
 
 const (
-	Request_RED	= 0
-	Request_GREEN	= 1
-	Request_BLUE	= 2
+	Request_RED   = 0
+	Request_GREEN = 1
+	Request_BLUE  = 2
 )
 
 var Request_Color_name = map[int32]string{
-	0:	"RED",
-	1:	"GREEN",
-	2:	"BLUE",
+	0: "RED",
+	1: "GREEN",
+	2: "BLUE",
 }
 var Request_Color_value = map[string]int32{
-	"RED":		0,
-	"GREEN":	1,
-	"BLUE":		2,
+	"RED":   0,
+	"GREEN": 1,
+	"BLUE":  2,
 }
 
 func NewRequest_Color(x int32) *Request_Color {
@@ -95,17 +95,17 @@
 type Reply_Entry_Game int32
 
 const (
-	Reply_Entry_FOOTBALL	= 1
-	Reply_Entry_TENNIS	= 2
+	Reply_Entry_FOOTBALL = 1
+	Reply_Entry_TENNIS   = 2
 )
 
 var Reply_Entry_Game_name = map[int32]string{
-	1:	"FOOTBALL",
-	2:	"TENNIS",
+	1: "FOOTBALL",
+	2: "TENNIS",
 }
 var Reply_Entry_Game_value = map[string]int32{
-	"FOOTBALL":	1,
-	"TENNIS":	2,
+	"FOOTBALL": 1,
+	"TENNIS":   2,
 }
 
 func NewReply_Entry_Game(x int32) *Reply_Entry_Game {
@@ -117,43 +117,40 @@
 }
 
 type Request struct {
-	Key			[]int64				"PB(varint,1,rep,name=key)"
-	ImportedMessage		*imp.ImportedMessage		"PB(bytes,2,opt,name=imported_message)"
-	Hue			*Request_Color			"PB(varint,3,opt,name=hue,enum=my_test.Request_Color)"
-	Hat			*HatType			"PB(varint,4,opt,name=hat,enum=my_test.HatType,def=1)"
-	Owner			*imp.ImportedMessage_Owner	"PB(varint,6,opt,name=owner,enum=imp.ImportedMessage_Owner)"
-	Deadline		*float32			"PB(fixed32,7,opt,name=deadline,def=inf)"
-	Somegroup		*Request_SomeGroup		"PB(group,8,opt,name=SomeGroup)"
-	XXX_unrecognized	[]byte
+	Key              []int64                    "PB(varint,1,rep,name=key)"
+	ImportedMessage  *imp.ImportedMessage       "PB(bytes,2,opt,name=imported_message)"
+	Hue              *Request_Color             "PB(varint,3,opt,name=hue,enum=my_test.Request_Color)"
+	Hat              *HatType                   "PB(varint,4,opt,name=hat,enum=my_test.HatType,def=1)"
+	Owner            *imp.ImportedMessage_Owner "PB(varint,6,opt,name=owner,enum=imp.ImportedMessage_Owner)"
+	Deadline         *float32                   "PB(fixed32,7,opt,name=deadline,def=inf)"
+	Somegroup        *Request_SomeGroup         "PB(group,8,opt,name=SomeGroup)"
+	XXX_unrecognized []byte
 }
 
-func (this *Request) Reset() {
-	*this = Request{}
-}
+func (this *Request) Reset()         { *this = Request{} }
+func (this *Request) String() string { return proto.CompactTextString(this) }
 
 const Default_Request_Hat HatType = HatType_FEDORA
 
 var Default_Request_Deadline float32 = float32(math.Inf(1))
 
 type Request_SomeGroup struct {
-	GroupField		*int32	"PB(varint,9,opt,name=group_field)"
-	XXX_unrecognized	[]byte
+	GroupField       *int32 "PB(varint,9,opt,name=group_field)"
+	XXX_unrecognized []byte
 }
 
-func (this *Request_SomeGroup) Reset() {
-	*this = Request_SomeGroup{}
-}
+func (this *Request_SomeGroup) Reset()         { *this = Request_SomeGroup{} }
+func (this *Request_SomeGroup) String() string { return proto.CompactTextString(this) }
 
 type Reply struct {
-	Found			[]*Reply_Entry	"PB(bytes,1,rep,name=found)"
-	CompactKeys		[]int32		"PB(varint,2,rep,packed,name=compact_keys)"
-	XXX_extensions		map[int32][]byte
-	XXX_unrecognized	[]byte
+	Found            []*Reply_Entry "PB(bytes,1,rep,name=found)"
+	CompactKeys      []int32        "PB(varint,2,rep,packed,name=compact_keys)"
+	XXX_extensions   map[int32][]byte
+	XXX_unrecognized []byte
 }
 
-func (this *Reply) Reset() {
-	*this = Reply{}
-}
+func (this *Reply) Reset()         { *this = Reply{} }
+func (this *Reply) String() string { return proto.CompactTextString(this) }
 
 var extRange_Reply = []proto.ExtensionRange{
 	proto.ExtensionRange{100, 536870911},
@@ -170,15 +167,14 @@
 }
 
 type Reply_Entry struct {
-	KeyThatNeeds_1234Camel_CasIng	*int64	"PB(varint,1,req,name=key_that_needs_1234camel_CasIng)"
-	Value				*int64	"PB(varint,2,opt,name=value,def=7)"
-	XMyFieldName_2			*int64	"PB(varint,3,opt,name=_my_field_name_2)"
-	XXX_unrecognized		[]byte
+	KeyThatNeeds_1234Camel_CasIng *int64 "PB(varint,1,req,name=key_that_needs_1234camel_CasIng)"
+	Value                         *int64 "PB(varint,2,opt,name=value,def=7)"
+	XMyFieldName_2                *int64 "PB(varint,3,opt,name=_my_field_name_2)"
+	XXX_unrecognized              []byte
 }
 
-func (this *Reply_Entry) Reset() {
-	*this = Reply_Entry{}
-}
+func (this *Reply_Entry) Reset()         { *this = Reply_Entry{} }
+func (this *Reply_Entry) String() string { return proto.CompactTextString(this) }
 
 const Default_Reply_Entry_Value int64 = 7
 
@@ -186,25 +182,24 @@
 	XXX_unrecognized []byte
 }
 
-func (this *ReplyExtensions) Reset() {
-	*this = ReplyExtensions{}
-}
+func (this *ReplyExtensions) Reset()         { *this = ReplyExtensions{} }
+func (this *ReplyExtensions) String() string { return proto.CompactTextString(this) }
 
 var E_ReplyExtensions_Time = &proto.ExtensionDesc{
-	ExtendedType:	(*Reply)(nil),
-	ExtensionType:	(*float64)(nil),
-	Field:		101,
-	Tag:		"PB(fixed64,101,opt,name=time)",
+	ExtendedType:  (*Reply)(nil),
+	ExtensionType: (*float64)(nil),
+	Field:         101,
+	Name:          "my_test.time",
+	Tag:           "PB(fixed64,101,opt,name=time)",
 }
 
 type OldReply struct {
-	XXX_extensions		map[int32][]byte
-	XXX_unrecognized	[]byte
+	XXX_extensions   map[int32][]byte
+	XXX_unrecognized []byte
 }
 
-func (this *OldReply) Reset() {
-	*this = OldReply{}
-}
+func (this *OldReply) Reset()         { *this = OldReply{} }
+func (this *OldReply) String() string { return proto.CompactTextString(this) }
 
 func (this *OldReply) Marshal() ([]byte, os.Error) {
 	return proto.MarshalMessageSet(this.ExtensionMap())
@@ -231,10 +226,11 @@
 }
 
 var E_Tag = &proto.ExtensionDesc{
-	ExtendedType:	(*Reply)(nil),
-	ExtensionType:	(*string)(nil),
-	Field:		103,
-	Tag:		"PB(bytes,103,opt,name=tag)",
+	ExtendedType:  (*Reply)(nil),
+	ExtensionType: (*string)(nil),
+	Field:         103,
+	Name:          "my_test.tag",
+	Tag:           "PB(bytes,103,opt,name=tag)",
 }
 
 func init() {
@@ -242,4 +238,6 @@
 	proto.RegisterEnum("my_test.Days", Days_name, Days_value)
 	proto.RegisterEnum("my_test.Request_Color", Request_Color_name, Request_Color_value)
 	proto.RegisterEnum("my_test.Reply_Entry_Game", Reply_Entry_Game_name, Reply_Entry_Game_value)
+	proto.RegisterExtension(E_ReplyExtensions_Time)
+	proto.RegisterExtension(E_Tag)
 }
diff --git a/proto/all_test.go b/proto/all_test.go
index 71d7e13..eb32155 100644
--- a/proto/all_test.go
+++ b/proto/all_test.go
@@ -1141,6 +1141,13 @@
 	}
 }
 
+// Enum types have String methods. Check that enum fields can be printed.
+// We don't care what the value actually is, just as long as it doesn't crash.
+func TestPrintingNilEnumFields(t *testing.T) {
+	pb := new(GoEnum)
+	fmt.Sprintf("%+v", pb)
+}
+
 // Verify that absent required fields cause Marshal/Unmarshal to return errors.
 func TestRequiredFieldEnforcement(t *testing.T) {
 	pb := new(GoTestField)
diff --git a/proto/encode.go b/proto/encode.go
index 5eab33b..7f242e1 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -94,11 +94,7 @@
 // protocol buffer types.
 func (p *Buffer) EncodeVarint(x uint64) os.Error {
 	l := len(p.buf)
-	if l+maxVarintBytes > cap(p.buf) { // not necessary except for performance
-		p.buf = append(p.buf, emptyBytes[:]...)
-	} else {
-		p.buf = p.buf[:l+maxVarintBytes]
-	}
+	p.buf = append(p.buf, emptyBytes[:]...)
 
 	for x >= 1<<7 {
 		p.buf[l] = uint8(x&0x7f | 0x80)
@@ -116,11 +112,7 @@
 func (p *Buffer) EncodeFixed64(x uint64) os.Error {
 	const fixed64Bytes = 8
 	l := len(p.buf)
-	if l+fixed64Bytes > cap(p.buf) { // not necessary except for performance
-		p.buf = append(p.buf, emptyBytes[:fixed64Bytes]...)
-	} else {
-		p.buf = p.buf[:l+fixed64Bytes]
-	}
+	p.buf = append(p.buf, emptyBytes[:fixed64Bytes]...)
 
 	p.buf[l] = uint8(x)
 	p.buf[l+1] = uint8(x >> 8)
@@ -139,11 +131,7 @@
 func (p *Buffer) EncodeFixed32(x uint64) os.Error {
 	const fixed32Bytes = 4
 	l := len(p.buf)
-	if l+fixed32Bytes > cap(p.buf) { // not necessary except for performance
-		p.buf = append(p.buf, emptyBytes[:fixed32Bytes]...)
-	} else {
-		p.buf = p.buf[:l+fixed32Bytes]
-	}
+	p.buf = append(p.buf, emptyBytes[:fixed32Bytes]...)
 
 	p.buf[l] = uint8(x)
 	p.buf[l+1] = uint8(x >> 8)
diff --git a/proto/extensions.go b/proto/extensions.go
index fe71709..4594c8b 100644
--- a/proto/extensions.go
+++ b/proto/extensions.go
@@ -39,6 +39,7 @@
 import (
 	"os"
 	"reflect"
+	"strconv"
 	"unsafe"
 )
 
@@ -60,6 +61,7 @@
 	ExtendedType  interface{} // nil pointer to the type that is being extended
 	ExtensionType interface{} // nil pointer to the extension type
 	Field         int32       // field number
+	Name          string      // fully-qualified name of extension
 	Tag           string      // PB(...) tag style
 }
 
@@ -179,3 +181,22 @@
 	pb.ExtensionMap()[extension.Field] = p.buf
 	return nil
 }
+
+// A global registry of extensions.
+// The generated code will register the generated descriptors by calling RegisterExtension.
+
+var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
+
+// RegisterExtension is called from the generated code.
+func RegisterExtension(desc *ExtensionDesc) {
+	st := reflect.TypeOf(desc.ExtendedType).Elem()
+	m := extensionMaps[st]
+	if m == nil {
+		m = make(map[int32]*ExtensionDesc)
+		extensionMaps[st] = m
+	}
+	if _, ok := m[desc.Field]; ok {
+		panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
+	}
+	m[desc.Field] = desc
+}
diff --git a/proto/testdata/test.pb.go b/proto/testdata/test.pb.go
index 5e4a8c6..eea8b69 100644
--- a/proto/testdata/test.pb.go
+++ b/proto/testdata/test.pb.go
@@ -123,9 +123,8 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoEnum) Reset() {
-	*this = GoEnum{}
-}
+func (this *GoEnum) Reset()		{ *this = GoEnum{} }
+func (this *GoEnum) String() string	{ return proto.CompactTextString(this) }
 
 type GoTestField struct {
 	Label			*string	"PB(bytes,1,req)"
@@ -133,9 +132,8 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoTestField) Reset() {
-	*this = GoTestField{}
-}
+func (this *GoTestField) Reset()		{ *this = GoTestField{} }
+func (this *GoTestField) String() string	{ return proto.CompactTextString(this) }
 
 type GoTest struct {
 	Kind			*int32			"PB(varint,1,req)"
@@ -213,9 +211,8 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoTest) Reset() {
-	*this = GoTest{}
-}
+func (this *GoTest) Reset()		{ *this = GoTest{} }
+func (this *GoTest) String() string	{ return proto.CompactTextString(this) }
 
 const Default_GoTest_F_BoolDefaulted bool = true
 const Default_GoTest_F_Int32Defaulted int32 = 32
@@ -238,27 +235,24 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoTest_RequiredGroup) Reset() {
-	*this = GoTest_RequiredGroup{}
-}
+func (this *GoTest_RequiredGroup) Reset()		{ *this = GoTest_RequiredGroup{} }
+func (this *GoTest_RequiredGroup) String() string	{ return proto.CompactTextString(this) }
 
 type GoTest_RepeatedGroup struct {
 	RequiredField		*string	"PB(bytes,81,req)"
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoTest_RepeatedGroup) Reset() {
-	*this = GoTest_RepeatedGroup{}
-}
+func (this *GoTest_RepeatedGroup) Reset()		{ *this = GoTest_RepeatedGroup{} }
+func (this *GoTest_RepeatedGroup) String() string	{ return proto.CompactTextString(this) }
 
 type GoTest_OptionalGroup struct {
 	RequiredField		*string	"PB(bytes,91,req)"
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoTest_OptionalGroup) Reset() {
-	*this = GoTest_OptionalGroup{}
-}
+func (this *GoTest_OptionalGroup) Reset()		{ *this = GoTest_OptionalGroup{} }
+func (this *GoTest_OptionalGroup) String() string	{ return proto.CompactTextString(this) }
 
 type GoSkipTest struct {
 	SkipInt32		*int32			"PB(varint,11,req,name=skip_int32)"
@@ -269,9 +263,8 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoSkipTest) Reset() {
-	*this = GoSkipTest{}
-}
+func (this *GoSkipTest) Reset()		{ *this = GoSkipTest{} }
+func (this *GoSkipTest) String() string	{ return proto.CompactTextString(this) }
 
 type GoSkipTest_SkipGroup struct {
 	GroupInt32		*int32	"PB(varint,16,req,name=group_int32)"
@@ -279,27 +272,24 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *GoSkipTest_SkipGroup) Reset() {
-	*this = GoSkipTest_SkipGroup{}
-}
+func (this *GoSkipTest_SkipGroup) Reset()		{ *this = GoSkipTest_SkipGroup{} }
+func (this *GoSkipTest_SkipGroup) String() string	{ return proto.CompactTextString(this) }
 
 type NonPackedTest struct {
 	A			[]int32	"PB(varint,1,rep,name=a)"
 	XXX_unrecognized	[]byte
 }
 
-func (this *NonPackedTest) Reset() {
-	*this = NonPackedTest{}
-}
+func (this *NonPackedTest) Reset()		{ *this = NonPackedTest{} }
+func (this *NonPackedTest) String() string	{ return proto.CompactTextString(this) }
 
 type PackedTest struct {
 	B			[]int32	"PB(varint,1,rep,packed,name=b)"
 	XXX_unrecognized	[]byte
 }
 
-func (this *PackedTest) Reset() {
-	*this = PackedTest{}
-}
+func (this *PackedTest) Reset()		{ *this = PackedTest{} }
+func (this *PackedTest) String() string	{ return proto.CompactTextString(this) }
 
 type InnerMessage struct {
 	Host			*string	"PB(bytes,1,req,name=host)"
@@ -308,9 +298,8 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *InnerMessage) Reset() {
-	*this = InnerMessage{}
-}
+func (this *InnerMessage) Reset()		{ *this = InnerMessage{} }
+func (this *InnerMessage) String() string	{ return proto.CompactTextString(this) }
 
 const Default_InnerMessage_Port int32 = 4000
 
@@ -322,9 +311,8 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *OtherMessage) Reset() {
-	*this = OtherMessage{}
-}
+func (this *OtherMessage) Reset()		{ *this = OtherMessage{} }
+func (this *OtherMessage) String() string	{ return proto.CompactTextString(this) }
 
 type MyMessage struct {
 	Count			*int32			"PB(varint,1,req,name=count)"
@@ -335,11 +323,25 @@
 	Others			[]*OtherMessage		"PB(bytes,6,rep,name=others)"
 	Bikeshed		*MyMessage_Color	"PB(varint,7,opt,name=bikeshed,enum=test_proto.MyMessage_Color)"
 	Somegroup		*MyMessage_SomeGroup	"PB(group,8,opt,name=SomeGroup)"
+	XXX_extensions		map[int32][]byte
 	XXX_unrecognized	[]byte
 }
 
-func (this *MyMessage) Reset() {
-	*this = MyMessage{}
+func (this *MyMessage) Reset()		{ *this = MyMessage{} }
+func (this *MyMessage) String() string	{ return proto.CompactTextString(this) }
+
+var extRange_MyMessage = []proto.ExtensionRange{
+	proto.ExtensionRange{100, 536870911},
+}
+
+func (*MyMessage) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_MyMessage
+}
+func (this *MyMessage) ExtensionMap() map[int32][]byte {
+	if this.XXX_extensions == nil {
+		this.XXX_extensions = make(map[int32][]byte)
+	}
+	return this.XXX_extensions
 }
 
 type MyMessage_SomeGroup struct {
@@ -347,31 +349,45 @@
 	XXX_unrecognized	[]byte
 }
 
-func (this *MyMessage_SomeGroup) Reset() {
-	*this = MyMessage_SomeGroup{}
-}
+func (this *MyMessage_SomeGroup) Reset()		{ *this = MyMessage_SomeGroup{} }
+func (this *MyMessage_SomeGroup) String() string	{ return proto.CompactTextString(this) }
 
-type MessageList struct {
-	Message			[]*MessageList_Message	"PB(group,1,rep,name=Message)"
+type Ext struct {
+	Data			*string	"PB(bytes,1,opt,name=data)"
 	XXX_unrecognized	[]byte
 }
 
-func (this *MessageList) Reset() {
-	*this = MessageList{}
+func (this *Ext) Reset()		{ *this = Ext{} }
+func (this *Ext) String() string	{ return proto.CompactTextString(this) }
+
+var E_Ext_More = &proto.ExtensionDesc{
+	ExtendedType:	(*MyMessage)(nil),
+	ExtensionType:	(*Ext)(nil),
+	Field:		103,
+	Name:		"test_proto.more",
+	Tag:		"PB(bytes,103,opt,name=more)",
 }
 
+type MessageList struct {
+	Message			[]*MessageList_Message	"PB(group,1,rep)"
+	XXX_unrecognized	[]byte
+}
+
+func (this *MessageList) Reset()		{ *this = MessageList{} }
+func (this *MessageList) String() string	{ return proto.CompactTextString(this) }
+
 type MessageList_Message struct {
 	Name			*string	"PB(bytes,2,req,name=name)"
 	Count			*int32	"PB(varint,3,req,name=count)"
 	XXX_unrecognized	[]byte
 }
 
-func (this *MessageList_Message) Reset() {
-	*this = MessageList_Message{}
-}
+func (this *MessageList_Message) Reset()		{ *this = MessageList_Message{} }
+func (this *MessageList_Message) String() string	{ return proto.CompactTextString(this) }
 
 func init() {
 	proto.RegisterEnum("test_proto.FOO", FOO_name, FOO_value)
 	proto.RegisterEnum("test_proto.GoTest_KIND", GoTest_KIND_name, GoTest_KIND_value)
 	proto.RegisterEnum("test_proto.MyMessage_Color", MyMessage_Color_name, MyMessage_Color_value)
+	proto.RegisterExtension(E_Ext_More)
 }
diff --git a/proto/testdata/test.proto b/proto/testdata/test.proto
index 5002d33..b3f4bd6 100644
--- a/proto/testdata/test.proto
+++ b/proto/testdata/test.proto
@@ -226,6 +226,16 @@
   optional group SomeGroup = 8 {
     optional int32 group_field = 9;
   }
+
+  extensions 100 to max;
+}
+
+message Ext {
+  extend MyMessage {
+    optional Ext more = 103;
+  }
+
+  optional string data = 1;
 }
 
 message MessageList {
diff --git a/proto/text.go b/proto/text.go
index 1ca46e8..bb32983 100644
--- a/proto/text.go
+++ b/proto/text.go
@@ -31,25 +31,26 @@
 
 package proto
 
-// Functions for writing the Text protocol buffer format.
-// TODO: message sets, extensions.
+// Functions for writing the text protocol buffer format.
+// TODO: message sets.
 
 import (
 	"bytes"
 	"fmt"
 	"io"
+	"log"
 	"os"
 	"reflect"
 	"strconv"
 	"strings"
 )
 
-// An io.Writer wrapper that tracks its indentation level.
+// textWriter is an io.Writer that tracks its indentation level.
 type textWriter struct {
-	indent_level int
-	complete     bool // if the current position is a complete line
-	compact      bool // whether to write out as a one-liner
-	writer       io.Writer
+	ind      int
+	complete bool // if the current position is a complete line
+	compact  bool // whether to write out as a one-liner
+	writer   io.Writer
 
 	c [1]byte // scratch
 }
@@ -63,15 +64,15 @@
 		return
 	}
 
-	for i := 0; i < len(frags); i++ {
+	for i, frag := range frags {
 		if w.complete {
-			for j := 0; j < w.indent_level; j++ {
+			for j := 0; j < w.ind; j++ {
 				w.writer.Write([]byte{' ', ' '})
 			}
 			w.complete = false
 		}
 
-		w.writer.Write([]byte(frags[i]))
+		w.writer.Write([]byte(frag))
 		if i+1 < len(frags) {
 			w.writer.Write([]byte{'\n'})
 		}
@@ -87,14 +88,14 @@
 	return err
 }
 
-func (w *textWriter) indent() { w.indent_level++ }
+func (w *textWriter) indent() { w.ind++ }
 
 func (w *textWriter) unindent() {
-	if w.indent_level == 0 {
-		fmt.Fprintln(os.Stderr, "proto: textWriter unindented too far!")
-	} else {
-		w.indent_level--
+	if w.ind == 0 {
+		log.Printf("proto: textWriter unindented too far!")
+		return
 	}
+	w.ind--
 }
 
 func writeName(w *textWriter, props *Properties) {
@@ -104,6 +105,8 @@
 	}
 }
 
+var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
+
 func writeStruct(w *textWriter, sv reflect.Value) {
 	st := sv.Type()
 	sprops := GetProperties(st)
@@ -125,15 +128,15 @@
 		}
 
 		if props.Repeated {
-			if av := fv; av.Kind() == reflect.Slice {
+			if fv.Kind() == reflect.Slice {
 				// Repeated field.
-				for j := 0; j < av.Len(); j++ {
+				for j := 0; j < fv.Len(); j++ {
 					writeName(w, props)
 					if !w.compact {
 						w.WriteByte(' ')
 					}
-					writeAny(w, av.Index(j), props)
-					fmt.Fprint(w, "\n")
+					writeAny(w, fv.Index(j), props)
+					w.WriteByte('\n')
 				}
 				continue
 			}
@@ -143,13 +146,23 @@
 		if !w.compact {
 			w.WriteByte(' ')
 		}
-		if len(props.Enum) == 0 || !tryWriteEnum(w, props.Enum, fv) {
+		if props.Enum != "" && tryWriteEnum(w, props.Enum, fv) {
+			// Enum written.
+		} else {
 			writeAny(w, fv, props)
 		}
-		fmt.Fprint(w, "\n")
+		w.WriteByte('\n')
+	}
+
+	// Extensions.
+	pv := sv.Addr()
+	if pv.Type().Implements(extendableProtoType) {
+		writeExtensions(w, pv)
 	}
 }
 
+// tryWriteEnum attempts to write an enum value as a symbolic constant.
+// If the enum is unregistered, nothing is written and false is returned.
 func tryWriteEnum(w *textWriter, enum string, v reflect.Value) bool {
 	v = reflect.Indirect(v)
 	if v.Type().Kind() != reflect.Int32 {
@@ -167,24 +180,20 @@
 	return true
 }
 
+// writeAny writes an arbitrary field.
 func writeAny(w *textWriter, v reflect.Value, props *Properties) {
 	v = reflect.Indirect(v)
 
 	// We don't attempt to serialise every possible value type; only those
 	// that can occur in protocol buffers, plus a few extra that were easy.
-	switch val := v; val.Kind() {
+	switch v.Kind() {
 	case reflect.Slice:
 		// Should only be a []byte; repeated fields are handled in writeStruct.
-		// TODO: Handle other cases more cleanly.
-		bytes := make([]byte, val.Len())
-		for i := 0; i < val.Len(); i++ {
-			bytes[i] = byte(val.Index(i).Uint())
-		}
-		// TODO: Should be strconv.QuoteC, which doesn't exist yet
-		fmt.Fprint(w, strconv.Quote(string(bytes)))
+		// TODO: Should be strconv.QuoteToASCII, which should be released after 2011-06-20.
+		fmt.Fprint(w, strconv.Quote(string(v.Interface().([]byte))))
 	case reflect.String:
-		// TODO: Should be strconv.QuoteC, which doesn't exist yet
-		fmt.Fprint(w, strconv.Quote(val.String()))
+		// TODO: Should be strconv.QuoteToASCII, which should be released after 2011-06-20.
+		fmt.Fprint(w, strconv.Quote(v.String()))
 	case reflect.Struct:
 		// Required/optional group/message.
 		var bra, ket byte = '<', '>'
@@ -196,11 +205,42 @@
 			w.WriteByte('\n')
 		}
 		w.indent()
-		writeStruct(w, val)
+		writeStruct(w, v)
 		w.unindent()
 		w.WriteByte(ket)
 	default:
-		fmt.Fprint(w, val.Interface())
+		fmt.Fprint(w, v.Interface())
+	}
+}
+
+// writeExtensions writes all the extensions in pv.
+// pv is assumed to be a pointer to a protocol message struct that is extendable.
+func writeExtensions(w *textWriter, pv reflect.Value) {
+	emap := extensionMaps[pv.Type().Elem()]
+	ep := pv.Interface().(extendableProto)
+	for extNum := range ep.ExtensionMap() {
+		var desc *ExtensionDesc
+		if emap != nil {
+			desc = emap[extNum]
+		}
+		if desc == nil {
+			// TODO: Handle printing unknown extensions.
+			fmt.Fprintln(os.Stderr, "proto: unknown extension: ", extNum)
+			continue
+		}
+
+		pb, err := GetExtension(ep, desc)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "proto: failed getting extension: ", err)
+			continue
+		}
+
+		fmt.Fprintf(w, "[%s]:", desc.Name)
+		if !w.compact {
+			w.WriteByte(' ')
+		}
+		writeAny(w, reflect.ValueOf(pb), nil)
+		w.WriteByte('\n')
 	}
 }
 
@@ -225,12 +265,12 @@
 	}
 }
 
-// MarshalText writes a given protobuffer in Text format.
-// Non-protobuffers can also be written, but their formatting is not guaranteed.
+// MarshalText writes a given protocol buffer in text format.
+// Values that are not protocol buffers can also be written, but their formatting is not guaranteed.
 func MarshalText(w io.Writer, pb interface{}) { marshalText(w, pb, false) }
 
-// CompactText writes a given protobuffer in compact Text format (one line).
-// Non-protobuffers can also be written, but their formatting is not guaranteed.
+// CompactText writes a given protocl buffer in compact text format (one line).
+// Values that are not protocol buffers can also be written, but their formatting is not guaranteed.
 func CompactText(w io.Writer, pb interface{}) { marshalText(w, pb, true) }
 
 // CompactTextString is the same as CompactText, but returns the string directly.
diff --git a/proto/text_test.go b/proto/text_test.go
index e31e21c..1427740 100644
--- a/proto/text_test.go
+++ b/proto/text_test.go
@@ -39,7 +39,7 @@
 )
 
 func newTestMessage() *MyMessage {
-	return &MyMessage{
+	msg := &MyMessage{
 		Count: Int32(42),
 		Name:  String("Dave"),
 		Quote: String(`"I didn't want to go."`),
@@ -67,6 +67,13 @@
 			GroupField: Int32(8),
 		},
 	}
+	ext := &Ext{
+		Data: String("Big gobs for big rats"),
+	}
+	if err := SetExtension(msg, E_Ext_More, ext); err != nil {
+		panic(err)
+	}
+	return msg
 }
 
 const text = `count: 42
@@ -95,6 +102,9 @@
 SomeGroup {
   group_field: 8
 }
+[test_proto.more]: <
+  data: "Big gobs for big rats"
+>
 `
 
 func TestMarshalTextFull(t *testing.T) {
@@ -107,7 +117,7 @@
 }
 
 func compact(src string) string {
-	// ,s/[ \n]+/ /g; s/ $//;
+	// s/[ \n]+/ /g; s/ $//;
 	dst := make([]byte, len(src))
 	space := false
 	j := 0