From 93743aa822c68627fd46e5fcac93576d9bdf2623 Mon Sep 17 00:00:00 2001
From: JCWasmx86 <JCWasmx86@t-online.de>
Date: Wed, 29 Jun 2022 13:43:01 +0200
Subject: [PATCH] Add more actions:

- AddThrowsDeclaration to add an error to the declaration of a method/constructor
- AddTryCatch around statements
- AddTryCatch around variable declarations
---
 src/codeaction/addthrowsdeclaration.vala      | 68 +++++++++++++++++++
 .../addtrycatchassignmentaction.vala          | 68 +++++++++++++++++++
 .../addtrycatchstatementaction.vala           | 61 +++++++++++++++++
 src/codehelp/codeaction.vala                  | 53 ++++++++++++++-
 src/codehelp/nodesearch.vala                  |  4 +-
 src/meson.build                               |  3 +
 src/server.vala                               |  2 +-
 7 files changed, 255 insertions(+), 4 deletions(-)
 create mode 100644 src/codeaction/addthrowsdeclaration.vala
 create mode 100644 src/codeaction/addtrycatchassignmentaction.vala
 create mode 100644 src/codeaction/addtrycatchstatementaction.vala

diff --git a/src/codeaction/addthrowsdeclaration.vala b/src/codeaction/addthrowsdeclaration.vala
new file mode 100644
index 00000000..f2a68746
--- /dev/null
+++ b/src/codeaction/addthrowsdeclaration.vala
@@ -0,0 +1,68 @@
+/* addthrowsdeclaration.vala
+ *
+ * Copyright 2022 JCWasmx86 <JCWasmx86@t-online.de>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+using Gee;
+using Lsp;
+
+class Vls.AddThrowsDeclaration : CodeAction {
+    public AddThrowsDeclaration (VersionedTextDocumentIdentifier document, string error_name, Vala.CodeNode where) {
+
+        var error_types = new Vala.HashSet<Vala.DataType> ();
+        if (where is Vala.Constructor)
+            ((Vala.Constructor)where).body.get_error_types (error_types, null);
+        else
+            where.get_error_types (error_types, null);
+
+        var sb = new StringBuilder ();
+        if (error_types.is_empty) {
+            sb.append (" throws ");
+        } else {
+            sb.append (", ");
+        }
+        sb.append (error_name);
+        sb.append (" ");
+        var sref = where.source_reference;
+        var range = new Range ();
+        for (var i = sref.begin.line; i <= sref.end.line; i++) {
+            var line = sref.file.get_source_line (i);
+            if (line.contains ("{")) {
+                var idx = line.index_of ("{");
+                range.start = new Lsp.Position () {
+                    line = i - 1,
+                    character = idx - 1,
+                };
+                range.end = new Lsp.Position () {
+                    line = i - 1,
+                    character = idx - 1,
+                };
+            }
+        }
+        var workspace_edit = new WorkspaceEdit ();
+        var document_edit = new TextDocumentEdit (document);
+        var text_edit = new TextEdit (range);
+        text_edit.range.end.character++;
+        text_edit.newText = sb.str;
+        document_edit.edits.add (text_edit);
+        workspace_edit.documentChanges = new ArrayList<TextDocumentEdit> ();
+        workspace_edit.documentChanges.add (document_edit);
+        this.edit = workspace_edit;
+        this.title = "Add to error list";
+    }
+}
diff --git a/src/codeaction/addtrycatchassignmentaction.vala b/src/codeaction/addtrycatchassignmentaction.vala
new file mode 100644
index 00000000..13007326
--- /dev/null
+++ b/src/codeaction/addtrycatchassignmentaction.vala
@@ -0,0 +1,68 @@
+/* AddTryCatchAssignmentAction.vala
+ *
+ * Copyright 2022 JCWasmx86 <JCWasmx86@t-online.de>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+using Gee;
+using Lsp;
+
+class Vls.AddTryCatchAssignmentAction : CodeAction {
+    public AddTryCatchAssignmentAction (VersionedTextDocumentIdentifier document, Vala.ArrayList<Vala.Variable> variables, string error_name, string indent, Vala.CodeNode rhs) {
+        var sref = rhs.source_reference;
+        var assignment_line = sref.file.get_source_line (sref.begin.line);
+        var copied_indent = assignment_line.substring (0, assignment_line.length - assignment_line.chug ().length);
+        var sb = new StringBuilder ();
+        foreach (var v in variables) {
+            sb.append (copied_indent).append (v.variable_type.to_string ()).append (" ").append (v.name).append (";\n");
+        }
+        sb.append (copied_indent).append ("try {\n");
+        sb.append (copied_indent).append (indent).append (variables[0].name).append (" = ");
+        var s1 = variables[0].initializer.source_reference;
+        for (var i = s1.begin.line; i <= s1.end.line; i++) {
+            var len = -1;
+            var offset = 0;
+            if (i == s1.begin.line && i != s1.end.line) {
+                offset = s1.begin.column - 1;
+            } else if (i == s1.end.line && i != s1.begin.line) {
+                len = s1.end.column;
+            } else if (i == s1.begin.line && i == s1.end.line) {
+                offset = s1.begin.column - 1;
+                len = s1.end.column - s1.begin.column + 1;
+            }
+            if (i != s1.begin.line)
+                sb.append (copied_indent).append (indent);
+            sb.append (s1.file.get_source_line (i).substring (offset, len).strip ());
+            sb.append (i == s1.end.line ? ";" : "").append ("\n");
+        }
+        // TODO: Deduplicate error name
+        sb.append (copied_indent).append ("} catch (").append (error_name).append (" e) {\n");
+        sb.append (copied_indent).append (indent).append ("error (\"Caught error ").append (error_name).append (": %s\", e.message);\n");
+        sb.append (copied_indent).append ("}\n");
+        var workspace_edit = new WorkspaceEdit ();
+        var document_edit = new TextDocumentEdit (document);
+        var text_edit = new TextEdit (new Range.from_sourceref (sref));
+        text_edit.range.start.character = 0;
+        text_edit.range.end.character++;
+        text_edit.newText = sb.str;
+        document_edit.edits.add (text_edit);
+        workspace_edit.documentChanges = new ArrayList<TextDocumentEdit> ();
+        workspace_edit.documentChanges.add (document_edit);
+        this.edit = workspace_edit;
+        this.title = "Wrap with try-catch";
+    }
+}
diff --git a/src/codeaction/addtrycatchstatementaction.vala b/src/codeaction/addtrycatchstatementaction.vala
new file mode 100644
index 00000000..f94f00a4
--- /dev/null
+++ b/src/codeaction/addtrycatchstatementaction.vala
@@ -0,0 +1,61 @@
+/* addtrycatchstatementaction.vala
+ *
+ * Copyright 2022 JCWasmx86 <JCWasmx86@t-online.de>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+using Gee;
+using Lsp;
+
+class Vls.AddTryCatchStatementAction : CodeAction {
+    public AddTryCatchStatementAction (VersionedTextDocumentIdentifier document, string error_name, string indent, Vala.CodeNode node) {
+        var sref = node.source_reference;
+        var sb = new StringBuilder ();
+        var line = sref.file.get_source_line (sref.begin.line);
+        var copied_indent = line.substring (0, line.length - line.chug ().length);
+        sb.append (copied_indent).append ("try {\n");
+        for (var i = sref.begin.line; i <= sref.end.line; i++) {
+            var len = -1;
+            var offset = 0;
+            if (i == sref.begin.line && i != sref.end.line) {
+                offset = sref.begin.column - 1;
+            } else if (i == sref.end.line && i != sref.begin.line) {
+                len = sref.end.column;
+            } else if (i == sref.begin.line && i == sref.end.line) {
+                offset = sref.begin.column - 1;
+                len = sref.end.column - sref.begin.column + 1;
+            }
+            sb.append (copied_indent).append (indent);
+            sb.append (sref.file.get_source_line (i).substring (offset, len).strip ());
+            sb.append (i == sref.end.line ? ";" : "").append ("\n");
+        }
+        sb.append (copied_indent).append ("} catch (").append (error_name).append (" e) {\n");
+        sb.append (copied_indent).append (indent).append ("error (\"Caught error ").append (error_name).append (": %s\", e.message);\n");
+        sb.append (copied_indent).append ("}\n");
+        var workspace_edit = new WorkspaceEdit ();
+        var document_edit = new TextDocumentEdit (document);
+        var text_edit = new TextEdit (new Range.from_sourceref (sref));
+        text_edit.range.start.character = 0;
+        text_edit.range.end.character++;
+        text_edit.newText = sb.str;
+        document_edit.edits.add (text_edit);
+        workspace_edit.documentChanges = new ArrayList<TextDocumentEdit> ();
+        workspace_edit.documentChanges.add (document_edit);
+        this.edit = workspace_edit;
+        this.title = "Wrap with try-catch";
+    }
+}
diff --git a/src/codehelp/codeaction.vala b/src/codehelp/codeaction.vala
index 4cb47f61..3a30713a 100644
--- a/src/codehelp/codeaction.vala
+++ b/src/codehelp/codeaction.vala
@@ -28,8 +28,9 @@ namespace Vls.CodeActions {
      * @param file      the current document
      * @param range     the range to show code actions for
      * @param uri       the document URI
+     * @param diags     the diagnostics to use
      */
-    Collection<CodeAction> extract (Compilation compilation, TextDocument file, Range range, string uri) {
+    Collection<CodeAction> extract (Compilation compilation, TextDocument file, Range range, string uri, Gee.List<Diagnostic> diags) {
         // search for nodes containing the query range
         var finder = new NodeSearch (file, range.start, true, range.end);
         var code_actions = new ArrayList<CodeAction> ();
@@ -60,6 +61,56 @@ namespace Vls.CodeActions {
             }
         }
 
+        finder = new NodeSearch.with_filter (file, null, (a, b) => true, true);
+        var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (file);
+        foreach (var d in diags) {
+            if (d.message.has_prefix ("unhandled error ")) {
+                foreach (var node in finder.result) {
+                    var tmp_range = new Range.from_sourceref (node.source_reference);
+                    if ((tmp_range.contains (d.range.start) && tmp_range.contains (d.range.end))
+                        && !(node is Vala.Block)) {
+                        var error_name = d.message.replace ("unhandled error `", "").replace ("'", "").strip ();
+                        if (node is DeclarationStatement) {
+                            var variables = new Vala.ArrayList<Variable> ();
+                            ((DeclarationStatement)node).get_defined_variables (variables);
+                            if (variables.size == 1)
+                                code_actions.add (new AddTryCatchAssignmentAction (document, variables, error_name, code_style.indentation, node));
+                            var parent = node.parent_node;
+                            while (parent != null) {
+                                if (parent is Vala.ForeachStatement || parent is Vala.LambdaExpression) {
+                                    parent = null;
+                                    break;
+                                }
+                                if (parent is Vala.Method || parent is Vala.Constructor)
+                                    break;
+                                parent = parent.parent_node;
+                            }
+                            if (parent != null) {
+                                code_actions.add (new AddThrowsDeclaration (document, error_name, parent));
+                            }
+                        } else if (node is ExpressionStatement && !(node.parent_node is Vala.ForeachStatement)) {
+                            var es = (ExpressionStatement) node;
+                            code_actions.add (new AddTryCatchStatementAction (document, error_name, code_style.indentation, es.expression));
+                        } else {
+                            var parent = node.parent_node;
+                            while (parent != null) {
+                                if (parent is Vala.ForeachStatement || parent is Vala.LambdaExpression) {
+                                    parent = null;
+                                    break;
+                                }
+                                if (parent is Vala.Method || parent is Vala.Constructor)
+                                    break;
+                                parent = parent.parent_node;
+                            }
+                            if (parent != null) {
+                                code_actions.add (new AddThrowsDeclaration (document, error_name, parent));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
         return code_actions;
     }
 
diff --git a/src/codehelp/nodesearch.vala b/src/codehelp/nodesearch.vala
index f4eb65d5..e4c5fdd9 100644
--- a/src/codehelp/nodesearch.vala
+++ b/src/codehelp/nodesearch.vala
@@ -33,7 +33,7 @@ class Vls.NodeSearch : Vala.CodeVisitor {
     private Gee.HashSet<Vala.CodeNode> seen = new Gee.HashSet<Vala.CodeNode> ();
 
     [CCode (has_target = false)]
-    public delegate bool Filter (Vala.CodeNode needle, Vala.CodeNode hay_node);
+    public delegate bool Filter (Vala.CodeNode? needle, Vala.CodeNode hay_node);
 
     private Vala.CodeNode? needle;
     private Filter? filter;
@@ -122,7 +122,7 @@ class Vls.NodeSearch : Vala.CodeVisitor {
         this.visit_source_file (file);
     }
 
-    public NodeSearch.with_filter (Vala.SourceFile file, Vala.CodeNode needle, Filter filter_func, 
+    public NodeSearch.with_filter (Vala.SourceFile file, Vala.CodeNode? needle, Filter filter_func,
                                    bool include_declaration = true) {
         this.file = file;
         this.needle = needle;
diff --git a/src/meson.build b/src/meson.build
index 2ecb932b..748815d8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -4,6 +4,9 @@ vls_src = files([
   'analysis/codestyleanalyzer.vala',
   'analysis/inlayhintnodes.vala',
   'analysis/symbolenumerator.vala',
+  'codeaction/addtrycatchassignmentaction.vala',
+  'codeaction/addtrycatchstatementaction.vala',
+  'codeaction/addthrowsdeclaration.vala',
   'codeaction/baseconverteraction.vala',
   'codeaction/implementmissingprereqsaction.vala',
   'codehelp/callhierarchy.vala',
diff --git a/src/server.vala b/src/server.vala
index 418817d6..349eacae 100644
--- a/src/server.vala
+++ b/src/server.vala
@@ -1664,7 +1664,7 @@ class Vls.Server : Jsonrpc.Server {
         var json_array = new Json.Array ();
 
         Vala.CodeContext.push (compilation.code_context);
-        var code_actions = CodeActions.extract (compilation, (TextDocument) source_file, p.range, Uri.unescape_string (p.textDocument.uri));
+        var code_actions = CodeActions.extract (compilation, (TextDocument) source_file, p.range, Uri.unescape_string (p.textDocument.uri), p.context.diagnostics);
         foreach (var action in code_actions)
             json_array.add_element (Json.gobject_serialize (action));
         Vala.CodeContext.pop ();