From 465e898ee4c2ac8cb37b73409527cac31ebd76ac Mon Sep 17 00:00:00 2001
From: x1unix
Date: Tue, 18 Aug 2020 17:10:46 +0300
Subject: [PATCH 1/5] ui: add F5 hotkey to run app
---
web/src/Header.tsx | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/web/src/Header.tsx b/web/src/Header.tsx
index 89468a37..427e2186 100644
--- a/web/src/Header.tsx
+++ b/web/src/Header.tsx
@@ -46,6 +46,17 @@ export class Header extends React.Component {
};
}
+ onKeyCommand = (e: KeyboardEvent) => {
+ switch (e.code) {
+ case 'F5':
+ e.preventDefault();
+ this.props.dispatch(runFileDispatcher);
+ return;
+ default:
+ return;
+ }
+ }
+
componentDidMount(): void {
const fileElement = document.createElement('input') as HTMLInputElement;
fileElement.type = 'file';
@@ -59,6 +70,13 @@ export class Header extends React.Component {
if (!version) return;
this.setState({showUpdateBanner: version !== config.appVersion});
}).catch(err => console.warn('failed to check server API version: ', err));
+
+ // register keybindings
+ window.addEventListener('keydown', this.onKeyCommand)
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('keydown', this.onKeyCommand);
}
onItemSelect() {
@@ -91,7 +109,8 @@ export class Header extends React.Component {
{
key: 'run',
text: 'Run',
- ariaLabel: 'Run',
+ ariaLabel: 'Run program (F5)',
+ title: 'Run program (F5)',
iconProps: {iconName: 'Play'},
disabled: this.props.loading,
onClick: () => {
From c06c6b130fb104e980c936d87835325c1a5e208d Mon Sep 17 00:00:00 2001
From: x1unix
Date: Tue, 18 Aug 2020 17:17:18 +0300
Subject: [PATCH 2/5] editor: manually filter related snippets
---
web/src/editor/provider.ts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/web/src/editor/provider.ts b/web/src/editor/provider.ts
index a7065a13..f60c9de8 100644
--- a/web/src/editor/provider.ts
+++ b/web/src/editor/provider.ts
@@ -52,13 +52,16 @@ class GoCompletionItemProvider implements monaco.languages.CompletionItemProvide
return Promise.resolve({suggestions: []});
}
+ // filter snippets by prefix.
+ // usually monaco does that but not always in right way
+ const relatedSnippets = snippets.filter(s => s.label.toString().startsWith(query.value))
try {
const resp = await this.client.getSuggestions(query);
- resp.suggestions = resp.suggestions ? snippets.concat(resp.suggestions) : snippets;
+ resp.suggestions = resp.suggestions ? relatedSnippets.concat(resp.suggestions) : relatedSnippets;
return resp;
} catch (err) {
console.error(`Failed to get code completion from server: ${err.message}`);
- return {suggestions: snippets};
+ return {suggestions: relatedSnippets};
}
}
}
From ac4f707a342e7c4a5e33b9aa19a12f7065463c9d Mon Sep 17 00:00:00 2001
From: x1unix
Date: Tue, 18 Aug 2020 19:17:20 +0300
Subject: [PATCH 3/5] analyzer: format insertText as snippet for functions
---
pkg/analyzer/completion_item.go | 15 +++++
pkg/analyzer/decl.go | 49 ++++++++++++--
pkg/analyzer/scanner.go | 2 +-
pkg/analyzer/scanner_test.go | 69 +++++++++++++-------
pkg/analyzer/testdata/src/example/example.go | 8 +++
5 files changed, 113 insertions(+), 30 deletions(-)
diff --git a/pkg/analyzer/completion_item.go b/pkg/analyzer/completion_item.go
index f7702006..18ae6b8e 100644
--- a/pkg/analyzer/completion_item.go
+++ b/pkg/analyzer/completion_item.go
@@ -35,6 +35,18 @@ const (
Snippet
)
+// CompletionItemInsertTextRule is insert text insert rule.
+type CompletionItemInsertTextRule int
+
+const (
+ // KeepWhitespace is adjust whitespace/indentation of
+ // multiline insert texts to match the current line indentation.
+ KeepWhitespace CompletionItemInsertTextRule = 1
+
+ // InsertAsSnippet means that `insertText` is a snippet.
+ InsertAsSnippet CompletionItemInsertTextRule = 4
+)
+
// CompletionItem is monaco-editor binding
type CompletionItem struct {
// Label is item label
@@ -47,6 +59,9 @@ type CompletionItem struct {
Documentation interface{} `json:"documentation"`
// InsertText is text to be inserted
InsertText string `json:"insertText"`
+ // InsertTextRules is a string or snippet that should be inserted in a document
+ // when selecting this completion. When `falsy` the label in used.
+ InsertTextRules CompletionItemInsertTextRule `json:"insertTextRules,omitempty"`
}
// MarkdownString is monaco-editor string with markdown
diff --git a/pkg/analyzer/decl.go b/pkg/analyzer/decl.go
index d70d1cdd..63acec7d 100644
--- a/pkg/analyzer/decl.go
+++ b/pkg/analyzer/decl.go
@@ -2,6 +2,7 @@ package analyzer
import (
"go/ast"
+ "strconv"
"strings"
)
@@ -77,14 +78,52 @@ func funcToString(fn *ast.FuncType) string {
return str
}
+// formatFuncInsertText returns `insertText` snippet template for monaco-editor
+// from function signature.
+//
+// Example:
+// func sum(a, b int) int
+// Will output:
+// "sum(${1:a}, ${2:b})"
+func formatFuncInsertText(fn *ast.FuncDecl) string {
+ sb := strings.Builder{}
+ sb.WriteString(fn.Name.String())
+ sb.WriteByte('(')
+
+ if fn.Type.Params != nil && fn.Type.Params.List != nil {
+ pos := 1 // in monaco, first input argument should start with 0
+ params := make([]string, 0, fn.Type.Params.NumFields())
+
+ for _, param := range fn.Type.Params.List {
+ if len(param.Names) == 0 {
+ // $pos
+ params = append(params, "$"+strconv.Itoa(pos))
+ pos++
+ continue
+ }
+
+ for _, p := range param.Names {
+ // ${pos:paramName}
+ params = append(params, "${"+strconv.Itoa(pos)+":"+p.Name+"}")
+ pos++
+ }
+ }
+ sb.WriteString(strings.Join(params, ", "))
+ }
+
+ sb.WriteRune(')')
+ return sb.String()
+}
+
func funcToItem(fn *ast.FuncDecl) *CompletionItem {
ci := &CompletionItem{
- Label: fn.Name.String(),
- Kind: Function,
- Documentation: formatDoc(fn.Doc.Text()),
+ Label: fn.Name.String(),
+ Kind: Function,
+ Detail: funcToString(fn.Type),
+ Documentation: formatDoc(fn.Doc.Text()),
+ InsertText: formatFuncInsertText(fn),
+ InsertTextRules: InsertAsSnippet,
}
- ci.Detail = funcToString(fn.Type)
- ci.InsertText = ci.Label + "()"
return ci
}
diff --git a/pkg/analyzer/scanner.go b/pkg/analyzer/scanner.go
index d25280db..81dab842 100644
--- a/pkg/analyzer/scanner.go
+++ b/pkg/analyzer/scanner.go
@@ -78,7 +78,7 @@ func (p *PackageScanner) appendFunc(fn *ast.FuncDecl, dest *SymbolIndex) {
}
item := funcToItem(fn)
- log.Debugf("found function '%s.%s'", p.name, item.InsertText)
+ log.Debugf("found function '%s.%s'", p.name, item.Detail)
dest.Append(item)
}
diff --git a/pkg/analyzer/scanner_test.go b/pkg/analyzer/scanner_test.go
index e282b27b..6e8f48b9 100644
--- a/pkg/analyzer/scanner_test.go
+++ b/pkg/analyzer/scanner_test.go
@@ -12,42 +12,63 @@ import (
var examplePackageSummary = PackageSummary{
Functions: newSymbolIndex([]*CompletionItem{
{
- Label: "SomeFunc",
- Kind: Function,
- Detail: "func(val string) string",
- InsertText: "SomeFunc()",
+ Label: "SomeFunc",
+ Kind: Function,
+ Detail: "func(val string) string",
+ InsertText: "SomeFunc(${1:val})",
+ InsertTextRules: InsertAsSnippet,
Documentation: NewMarkdownString(
"SomeFunc is test function sample\nwith doc that contains code sample:" +
"\n\n```\n\ta := \"foo\"\n\tfmt.PrintLn(a)\n\n```\nend\n\n",
),
},
{
- Label: "ChanArrFunc",
- Kind: Function,
- Detail: "func(items ...string) chan string",
- InsertText: "ChanArrFunc()",
- Documentation: NewMarkdownString("ChanArrFunc is stub\n\n"),
+ Label: "ChanArrFunc",
+ Kind: Function,
+ Detail: "func(items ...string) chan string",
+ InsertText: "ChanArrFunc(${1:items})",
+ InsertTextRules: InsertAsSnippet,
+ Documentation: NewMarkdownString("ChanArrFunc is stub\n\n"),
},
{
- Label: "SomeFunc2",
- Kind: Function,
- Detail: "func(m map[string]interface{}, v *int) []interface{}",
- InsertText: "SomeFunc2()",
- Documentation: NewMarkdownString("SomeFunc2 is func stub\n\n"),
+ Label: "SomeFunc2",
+ Kind: Function,
+ Detail: "func(m map[string]interface{}, v *int) []interface{}",
+ InsertText: "SomeFunc2(${1:m}, ${2:v})",
+ InsertTextRules: InsertAsSnippet,
+ Documentation: NewMarkdownString("SomeFunc2 is func stub\n\n"),
},
{
- Label: "IfaceFunc",
- Kind: Function,
- Detail: "func() Action",
- InsertText: "IfaceFunc()",
- Documentation: NewMarkdownString("IfaceFunc is stub with unterminated code block\n```\n\t2 + 2\n\n```\n"),
+ Label: "IfaceFunc",
+ Kind: Function,
+ Detail: "func() Action",
+ InsertText: "IfaceFunc()",
+ InsertTextRules: InsertAsSnippet,
+ Documentation: NewMarkdownString("IfaceFunc is stub with unterminated code block\n```\n\t2 + 2\n\n```\n"),
},
{
- Label: "FuncReturnFuncAndIface",
- Kind: Function,
- Detail: "func() (func() (string, error), interface{\nf func()\n})",
- InsertText: "FuncReturnFuncAndIface()",
- Documentation: NewMarkdownString("FuncReturnFuncAndIface is stub\n\n"),
+ Label: "FuncReturnFuncAndIface",
+ Kind: Function,
+ Detail: "func() (func() (string, error), interface{\nf func()\n})",
+ InsertText: "FuncReturnFuncAndIface()",
+ InsertTextRules: InsertAsSnippet,
+ Documentation: NewMarkdownString("FuncReturnFuncAndIface is stub\n\n"),
+ },
+ {
+ Label: "XXX",
+ Kind: Function,
+ Detail: "func(a string, b string)",
+ InsertText: "XXX(${1:a}, ${2:b})",
+ InsertTextRules: InsertAsSnippet,
+ Documentation: NewMarkdownString("XXX is function example\n\n"),
+ },
+ {
+ Label: "FuncUnnamedParams",
+ Kind: Function,
+ Detail: "func(string)",
+ InsertText: "FuncUnnamedParams($1)",
+ InsertTextRules: InsertAsSnippet,
+ Documentation: NewMarkdownString("FuncUnnamedParams is function with unnamed params\n\n"),
},
}),
Values: newSymbolIndex([]*CompletionItem{
diff --git a/pkg/analyzer/testdata/src/example/example.go b/pkg/analyzer/testdata/src/example/example.go
index f88a7947..377c0d1a 100644
--- a/pkg/analyzer/testdata/src/example/example.go
+++ b/pkg/analyzer/testdata/src/example/example.go
@@ -77,3 +77,11 @@ func IfaceFunc() Action {
func FuncReturnFuncAndIface() (func() (string, error), interface{ f() }) {
return nil, nil
}
+
+// XXX is function example
+func XXX(a, b string) {
+
+}
+
+// FuncUnnamedParams is function with unnamed params
+func FuncUnnamedParams(string) {}
From 16b292fbe2d385618bfbe988496b3691da2ec698 Mon Sep 17 00:00:00 2001
From: x1unix
Date: Tue, 18 Aug 2020 19:19:06 +0300
Subject: [PATCH 4/5] editor: add fix snippets indentation
---
web/src/editor/provider.ts | 26 ++++++++++++++++++++++----
web/src/editor/snippets.ts | 9 ++-------
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/web/src/editor/provider.ts b/web/src/editor/provider.ts
index f60c9de8..19152ca9 100644
--- a/web/src/editor/provider.ts
+++ b/web/src/editor/provider.ts
@@ -52,13 +52,31 @@ class GoCompletionItemProvider implements monaco.languages.CompletionItemProvide
return Promise.resolve({suggestions: []});
}
+ let word = model.getWordUntilPosition(position);
+ let range = {
+ startLineNumber: position.lineNumber,
+ endLineNumber: position.lineNumber,
+ startColumn: word.startColumn,
+ endColumn: word.endColumn
+ };
+
// filter snippets by prefix.
// usually monaco does that but not always in right way
- const relatedSnippets = snippets.filter(s => s.label.toString().startsWith(query.value))
+ const relatedSnippets = snippets
+ .filter(s => s.label.toString().startsWith(query.value))
+ .map(s => ({...s, range}));
+
try {
- const resp = await this.client.getSuggestions(query);
- resp.suggestions = resp.suggestions ? relatedSnippets.concat(resp.suggestions) : relatedSnippets;
- return resp;
+ const {suggestions} = await this.client.getSuggestions(query);
+ if (!suggestions) {
+ return {
+ suggestions: relatedSnippets
+ }
+ }
+
+ return {
+ suggestions: relatedSnippets.concat(suggestions.map(s => ({...s, range})))
+ }
} catch (err) {
console.error(`Failed to get code completion from server: ${err.message}`);
return {suggestions: relatedSnippets};
diff --git a/web/src/editor/snippets.ts b/web/src/editor/snippets.ts
index 7c7ee405..cffa732d 100644
--- a/web/src/editor/snippets.ts
+++ b/web/src/editor/snippets.ts
@@ -2,12 +2,7 @@ import * as monaco from 'monaco-editor';
/* eslint-disable no-template-curly-in-string */
-// Import aliases
-type CompletionList = monaco.languages.CompletionList;
-type CompletionContext = monaco.languages.CompletionContext;
-type ITextModel = monaco.editor.ITextModel;
-type Position = monaco.Position;
-
+const Rule = monaco.languages.CompletionItemInsertTextRule;
/**
* List of snippets for editor
*/
@@ -121,7 +116,7 @@ const snippets = [
].map(s => ({
kind: monaco.languages.CompletionItemKind.Snippet,
- insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+ insertTextRules: Rule.InsertAsSnippet,
...s,
}));
From 73e185f7bc1974a6094fa5526e6d59797627eee3 Mon Sep 17 00:00:00 2001
From: x1unix
Date: Tue, 18 Aug 2020 19:39:58 +0300
Subject: [PATCH 5/5] update changelog for 1.6
---
web/src/ChangeLogModal.tsx | 23 +++--------------------
1 file changed, 3 insertions(+), 20 deletions(-)
diff --git a/web/src/ChangeLogModal.tsx b/web/src/ChangeLogModal.tsx
index 96c2c214..2f06a492 100644
--- a/web/src/ChangeLogModal.tsx
+++ b/web/src/ChangeLogModal.tsx
@@ -39,31 +39,14 @@ export default function ChangeLogModal(props: ChangeLogModalProps) {
Interface - Global
- - Added list of snippets with templates and tutorials near Open menu item
- - Moved Settings menu button from drop-down to main section
-
-
-
- Interface - Settings
-
- - Added editor fonts selector
- - Added support of font code ligatures
- - Fixed fallback font issue that might cause issues on Linux
+ - Add F5 hotkey for Run action
Interface - Editor
- -
- Added code snippets to make code input faster:
-
- iferr - Error handling snippet
- switch - Quick switch declaration
- typestruct - Quickly declare struct
- fmtprintf - fmt.Printf shorthand
- - and other (see release notes)
-
-
+ - Suggest function arguments completion
+ - Fix snippets suggestion issue