Repository: blevesearch/bleve (go)

Issue:
Feature request: Add `NewKeywordFieldMapping()` for `keyword` field mapping
Dear all,

Currently we have to use code like below to add a `keyword` type field mapping:

```
m := bleve.NewTextFieldMapping()
m.Analyzer = "keyword"
```

This is not mentioned in official document (http://blevesearch.com/docs/Index-Mapping/), but i found it in CouchBase Blog.
Can we have a new function, e.g. `NewKeywordFieldMapping()` for this? Just like the others:

```
func NewBooleanFieldMapping() *mapping.FieldMapping
func NewNumericFieldMapping() *mapping.FieldMapping
```

Pull request description:
Introduce `NewKeywordFieldMapping()`.
A keyword field mapping is a text field with `keyword` analyzer. It will be easier for users who are migrating from ElasticSearch.

closes #1633 

Dear @abhinavdangeti, could you help review and give your opinions?

p.s. i signed the CLA.

Patch:
diff --git a/mapping.go b/mapping.go
index 54753073..aa425744 100644
--- a/mapping.go
+++ b/mapping.go
@@ -45,6 +45,12 @@ func NewTextFieldMapping() *mapping.FieldMapping {
 	return mapping.NewTextFieldMapping()
 }
 
+// NewKeywordFieldMapping returns a field mapping for text using the keyword
+// analyzer, which essentially doesn't apply any specific text analysis.
+func NewKeywordFieldMapping() *mapping.FieldMapping {
+	return mapping.NewKeywordFieldMapping()
+}
+
 // NewNumericFieldMapping returns a default field mapping for numbers
 func NewNumericFieldMapping() *mapping.FieldMapping {
 	return mapping.NewNumericFieldMapping()
diff --git a/mapping/document.go b/mapping/document.go
index 44911de7..56eb81cd 100644
--- a/mapping/document.go
+++ b/mapping/document.go
@@ -528,7 +528,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
 			switch property := property.(type) {
 			case encoding.TextMarshaler:
 				// ONLY process TextMarshaler if there is an explicit mapping
-				// AND all of the fiels are of type text
+				// AND all of the fields are of type text
 				// OTHERWISE process field without TextMarshaler
 				if subDocMapping != nil {
 					allFieldsText := true
diff --git a/mapping/field.go b/mapping/field.go
index 7ad1744d..907d4c4b 100644
--- a/mapping/field.go
+++ b/mapping/field.go
@@ -19,6 +19,7 @@ import (
 	"fmt"
 	"time"
 
+	"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
 	index "github.com/blevesearch/bleve_index_api"
 
 	"github.com/blevesearch/bleve/v2/analysis"
@@ -89,6 +90,19 @@ func newTextFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
 	return rv
 }
 
+// NewKeyworFieldMapping returns a default field mapping for text with analyzer "keyword".
+func NewKeywordFieldMapping() *FieldMapping {
+	return &FieldMapping{
+		Type:               "text",
+		Analyzer:           keyword.Name,
+		Store:              true,
+		Index:              true,
+		IncludeTermVectors: true,
+		IncludeInAll:       true,
+		DocValues:          true,
+	}
+}
+
 // NewNumericFieldMapping returns a default field mapping for numbers
 func NewNumericFieldMapping() *FieldMapping {
 	return &FieldMapping{
diff --git a/search/highlight/fragmenter/simple/simple.go b/search/highlight/fragmenter/simple/simple.go
index 348dc1ce..34e5c959 100644
--- a/search/highlight/fragmenter/simple/simple.go
+++ b/search/highlight/fragmenter/simple/simple.go
@@ -123,9 +123,15 @@ OUTER:
 		// if there were no terms to highlight
 		// produce a single fragment from the beginning
 		start := 0
-		end := start + s.fragmentSize
-		if end > len(orig) {
-			end = len(orig)
+		end := start
+		used := 0
+		for end < len(orig) && used < s.fragmentSize {
+			r, size := utf8.DecodeRune(orig[end:])
+			if r == utf8.RuneError {
+				break
+			}
+			end += size
+			used++
 		}
 		rv = append(rv, &highlight.Fragment{Orig: orig, Start: start, End: end})
 	}


Test patch:
diff --git a/search/highlight/fragmenter/simple/simple_test.go b/search/highlight/fragmenter/simple/simple_test.go
index dcf4bf83..5106b652 100644
--- a/search/highlight/fragmenter/simple/simple_test.go
+++ b/search/highlight/fragmenter/simple/simple_test.go
@@ -285,6 +285,17 @@ func TestSimpleFragmenterWithSize(t *testing.T) {
 				},
 			},
 		},
+		{
+			orig: []byte("避免出现 rune 越界问题"),
+			fragments: []*highlight.Fragment{
+				{
+					Orig:  []byte("避免出现 rune 越界问题"),
+					Start: 0,
+					End:   13,
+				},
+			},
+			ot: nil,
+		},
 	}
 
 	fragmenter := NewFragmenter(5)