github.com/jmigpin/editor@v1.6.0/core/lsproto/util.go (about)

     1  package lsproto
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  	"strings"
    15  	"unicode/utf16"
    16  
    17  	"github.com/jmigpin/editor/util/iout/iorw"
    18  	"github.com/jmigpin/editor/util/osutil"
    19  	"github.com/jmigpin/editor/util/parseutil"
    20  )
    21  
    22  //----------
    23  
    24  var logger0 = log.New(os.Stdout, "", log.Lshortfile)
    25  
    26  func logTestVerbose() bool {
    27  	f := flag.Lookup("test.v")
    28  	return f != nil && f.Value.String() == "true"
    29  }
    30  
    31  func logPrintf(f string, args ...interface{}) {
    32  	if !logTestVerbose() {
    33  		return
    34  	}
    35  	logger0.Output(2, fmt.Sprintf(f, args...))
    36  }
    37  
    38  func logJson(prefix string, v interface{}) {
    39  	if !logTestVerbose() {
    40  		return
    41  	}
    42  	b, err := json.MarshalIndent(v, "", "\t")
    43  	if err != nil {
    44  		panic(err)
    45  	}
    46  	logger0.Output(2, fmt.Sprintf("%v%v", prefix, string(b)))
    47  }
    48  
    49  //----------
    50  
    51  func encodeJson(a interface{}) ([]byte, error) {
    52  	buf := &bytes.Buffer{}
    53  	enc := json.NewEncoder(buf)
    54  	err := enc.Encode(a)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	b := buf.Bytes()
    59  	return b, nil
    60  }
    61  
    62  func decodeJson(r io.Reader, a interface{}) error {
    63  	dec := json.NewDecoder(r)
    64  	return dec.Decode(a)
    65  }
    66  func decodeJsonRaw(raw json.RawMessage, a interface{}) error {
    67  	return json.Unmarshal(raw, a)
    68  }
    69  
    70  //----------
    71  
    72  func Utf16Column(rd iorw.ReaderAt, lineStartOffset, utf8Col int) (int, error) {
    73  	b, err := rd.ReadFastAt(lineStartOffset, utf8Col)
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  	return len(utf16.Encode([]rune(string(b)))), nil
    78  }
    79  
    80  // Input and result is zero based.
    81  func Utf8Column(rd iorw.ReaderAt, lineStartOffset, utf16Col int) (int, error) {
    82  	// ensure good limits
    83  	n := utf16Col * 2
    84  	if lineStartOffset+n > rd.Max() {
    85  		n = rd.Max() - lineStartOffset
    86  	}
    87  
    88  	b, err := rd.ReadFastAt(lineStartOffset, n)
    89  	if err != nil {
    90  		return 0, err
    91  	}
    92  
    93  	enc := utf16.Encode([]rune(string(b)))
    94  	if len(enc) < utf16Col {
    95  		return 0, fmt.Errorf("encoded string smaller then utf16col")
    96  	}
    97  	nthChar := len(enc[:utf16Col])
    98  
    99  	return nthChar, nil
   100  }
   101  
   102  //----------
   103  
   104  func OffsetToPosition(rd iorw.ReaderAt, offset int) (Position, error) {
   105  	l, c, err := parseutil.IndexLineColumn(rd, offset)
   106  	if err != nil {
   107  		return Position{}, err
   108  	}
   109  	// zero based
   110  	l, c = l-1, c-1
   111  
   112  	// character offset in utf16
   113  	c2, err := Utf16Column(rd, offset-c, c)
   114  	if err != nil {
   115  		return Position{}, err
   116  	}
   117  
   118  	return Position{Line: l, Character: c2}, nil
   119  }
   120  
   121  func RangeToOffsetLen(rd iorw.ReaderAt, rang *Range) (int, int, error) {
   122  	l1, _ := rang.Start.OneBased()
   123  	l2, _ := rang.End.OneBased()
   124  
   125  	// line start offset
   126  	// TODO: improve getting lso2
   127  	lso1, err := parseutil.LineColumnIndex(rd, l1, 1)
   128  	if err != nil {
   129  		return 0, 0, err
   130  	}
   131  	lso2, err := parseutil.LineColumnIndex(rd, l2, 1)
   132  	if err != nil {
   133  		return 0, 0, err
   134  	}
   135  
   136  	// translate utf16 columns to utf8 (input and results are zero based)
   137  	u16c1, err := Utf8Column(rd, lso1, rang.Start.Character)
   138  	if err != nil {
   139  		return 0, 0, err
   140  	}
   141  	u16c2, err := Utf8Column(rd, lso2, rang.End.Character)
   142  	if err != nil {
   143  		return 0, 0, err
   144  	}
   145  
   146  	// start/end (range)
   147  	start := lso1 + u16c1
   148  	end := lso2 + u16c2
   149  
   150  	offset := start
   151  	length := end - start
   152  
   153  	return offset, length, nil
   154  }
   155  
   156  //----------
   157  
   158  func JsonGetPath(v interface{}, path string) (interface{}, error) {
   159  	args := strings.Split(path, ".")
   160  	return jsonGetPath2(v, args)
   161  }
   162  
   163  // TODO: incomplete
   164  func jsonGetPath2(v interface{}, args []string) (interface{}, error) {
   165  	// handle last arg
   166  	if len(args) == 0 {
   167  		switch t := v.(type) {
   168  		case bool, int, float32, float64:
   169  			return t, nil
   170  		}
   171  		return nil, fmt.Errorf("unhandled last type: %T", v)
   172  	}
   173  	// handle args: len(args)>0
   174  	arg, args2 := args[0], args[1:]
   175  	switch t := v.(type) {
   176  	case map[string]interface{}:
   177  		if v, ok := t[arg]; ok {
   178  			return jsonGetPath2(v, args2)
   179  		}
   180  		return nil, fmt.Errorf("not found: %v", arg)
   181  	}
   182  	return nil, fmt.Errorf("unhandled type: %T (arg=%v)", v, arg)
   183  }
   184  
   185  //----------
   186  
   187  func UrlToAbsFilename(url string) (string, error) {
   188  	return parseutil.UrlToAbsFilename(url)
   189  }
   190  
   191  func AbsFilenameToUrl(filename string) (string, error) {
   192  	if runtime.GOOS == "windows" {
   193  		// gopls requires casing to match the OS names in windows (error: case mismatch in path ...)
   194  		if u, err := osutil.FsCaseFilename(filename); err == nil {
   195  			filename = u
   196  		}
   197  	}
   198  	return parseutil.AbsFilenameToUrl(filename)
   199  }
   200  
   201  //----------
   202  
   203  type ManagerCallHierarchyCalls struct {
   204  	item  *CallHierarchyItem
   205  	calls []*CallHierarchyCall
   206  }
   207  
   208  func ManagerCallHierarchyCallsToString(mcalls []*ManagerCallHierarchyCalls, typ CallHierarchyCallType, baseDir string) (string, error) {
   209  	res := []string{}
   210  
   211  	// build title
   212  	s1 := "incoming"
   213  	if typ == OutgoingChct {
   214  		s1 = "outgoing"
   215  	}
   216  	u := fmt.Sprintf("lsproto call hierarchy %s calls:", s1)
   217  	res = append(res, u)
   218  
   219  	for _, mcall := range mcalls {
   220  		// build subtitle
   221  		s2 := "to"
   222  		if typ == OutgoingChct {
   223  			s2 = "from"
   224  		}
   225  		// count results for subtitle
   226  		nres := 0
   227  		for _, call := range mcall.calls {
   228  			nres += len(call.FromRanges)
   229  		}
   230  		s3 := fmt.Sprintf("calls %s %v: %v results", s2, mcall.item.Name, nres)
   231  		res = append(res, s3)
   232  
   233  		res2 := []string{}
   234  		for _, call := range mcall.calls {
   235  			item := call.Item()
   236  
   237  			// item for filename
   238  			fileItem := item
   239  			if typ == OutgoingChct {
   240  				fileItem = mcall.item
   241  			}
   242  			filename, err := UrlToAbsFilename(string(fileItem.Uri))
   243  			if err != nil {
   244  				return "", err
   245  			}
   246  			// use basedir to output filename
   247  			if baseDir != "" {
   248  				if u, err := filepath.Rel(baseDir, filename); err == nil {
   249  					filename = u
   250  				}
   251  			}
   252  
   253  			for _, r := range call.FromRanges {
   254  				line, col := r.Start.OneBased()
   255  				u := fmt.Sprintf("\t%s:%d:%d: %s", filename, line, col, item.Name)
   256  				res2 = append(res2, u)
   257  			}
   258  		}
   259  		sort.Strings(res2)
   260  		res = append(res, res2...)
   261  	}
   262  	w := strings.Join(res, "\n")
   263  	return w, nil
   264  }
   265  
   266  //----------
   267  
   268  func LocationsToString(locations []*Location, baseDir string) (string, error) {
   269  	res := []string{}
   270  	for _, loc := range locations {
   271  		filename, err := UrlToAbsFilename(string(loc.Uri))
   272  		if err != nil {
   273  			return "", err
   274  		}
   275  
   276  		// use basedir to output filename
   277  		if baseDir != "" {
   278  			if u, err := filepath.Rel(baseDir, filename); err == nil {
   279  				filename = u
   280  			}
   281  		}
   282  
   283  		line, col := loc.Range.Start.OneBased()
   284  		u := fmt.Sprintf("\t%v:%v:%v\n", filename, line, col)
   285  		res = append(res, u)
   286  	}
   287  	sort.Strings(res)
   288  	return strings.Join(res, ""), nil
   289  }
   290  
   291  //----------
   292  
   293  func CompletionListToString(clist *CompletionList) []string {
   294  	res := []string{}
   295  	for _, ci := range clist.Items {
   296  		u := []string{}
   297  		if ci.Deprecated {
   298  			u = append(u, "*deprecated*")
   299  		}
   300  		ci.Label = strings.TrimSpace(ci.Label) // NOTE: clangd is sending with spaces
   301  		u = append(u, ci.Label)
   302  		if ci.Detail != "" {
   303  			u = append(u, ci.Detail)
   304  		}
   305  		res = append(res, strings.Join(u, " "))
   306  	}
   307  
   308  	//// add documentation if there is only 1 result
   309  	//if len(compList.Items) == 1 {
   310  	//	doc := compList.Items[0].Documentation
   311  	//	if doc != "" {
   312  	//		res[0] += "\n\n" + doc
   313  	//	}
   314  	//}
   315  
   316  	return res
   317  }
   318  
   319  //----------
   320  
   321  func PatchTextEdits(src []byte, edits []*TextEdit) ([]byte, error) {
   322  	sortTextEdits(edits)
   323  	res := bytes.Buffer{} // resulting patched src
   324  	rd := iorw.NewBytesReadWriterAt(src)
   325  	start := 0
   326  	for _, e := range edits {
   327  		offset, n, err := RangeToOffsetLen(rd, e.Range)
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  		res.Write(src[start:offset])
   332  		res.Write([]byte(e.NewText))
   333  		start = offset + n
   334  	}
   335  	res.Write(src[start:]) // rest of the src
   336  	return res.Bytes(), nil
   337  }
   338  
   339  func sortTextEdits(edits []*TextEdit) {
   340  	sort.Slice(edits, func(i, j int) bool {
   341  		p1, p2 := &edits[i].Range.Start, &edits[j].Range.Start
   342  		return p1.Line < p2.Line ||
   343  			(p1.Line == p2.Line && p1.Character <= p2.Character)
   344  	})
   345  }
   346  
   347  //----------