├── .gitignore
├── META-INF
└── plugin.xml
├── demo
├── clk.gif
├── findViewById.gif
├── inflateFindViewById.gif
└── toast.gif
├── readme.md
└── src
├── org
└── sssta
│ └── androidtools
│ ├── action
│ ├── AbstractFvbiGeneratorAction.java
│ ├── FieldsFvbiGeneratorAction.java
│ ├── LocalFvbiGeneratorAction.java
│ └── generator
│ │ ├── AbstractFvbiGenerator.java
│ │ ├── FieldsFvbiGenerator.java
│ │ └── LocalFvbiGenerator.java
│ ├── model
│ └── ViewModel.java
│ ├── posfix
│ ├── AndroidPostfixTemplateProvider.java
│ ├── internal
│ │ ├── AbstractRichStringBasedPostfixTemplate.java
│ │ ├── RichChooserStringBasedPostfixTemplate.java
│ │ └── RichTopmostStringBasedPostfixTemplate.java
│ ├── macro
│ │ ├── TagMacro.java
│ │ └── ViewListenerMacro.java
│ └── template
│ │ ├── ClickListenerPostfixTemplate.java
│ │ └── ToastPostfixTemplate.java
│ └── util
│ ├── AndroidFQClass.java
│ ├── AndroidPostfixTemplatesUtils.java
│ ├── CommonUtil.java
│ ├── ImportUtil.java
│ ├── LayoutUtil.java
│ └── Levenshtein.java
└── postfixTemplates
├── ClickListenerPostfixTemplate
└── description.html
└── ToastPostfixTemplate
└── description.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion
4 |
5 | *.iml
6 |
7 | ## Directory-based project format:
8 | .idea/
9 | # if you remove the above rule, at least ignore the following:
10 |
11 | # User-specific stuff:
12 | # .idea/workspace.xml
13 | # .idea/tasks.xml
14 | # .idea/dictionaries
15 |
16 | # Sensitive or high-churn files:
17 | # .idea/dataSources.ids
18 | # .idea/dataSources.xml
19 | # .idea/sqlDataSources.xml
20 | # .idea/dynamic.xml
21 | # .idea/uiDesigner.xml
22 |
23 | # Gradle:
24 | # .idea/gradle.xml
25 | # .idea/libraries
26 |
27 | # Mongo Explorer plugin:
28 | # .idea/mongoSettings.xml
29 |
30 | ## File-based project format:
31 | *.ipr
32 | *.iws
33 |
34 | ## Plugin-specific files:
35 |
36 | # IntelliJ
37 | /out/
38 |
39 | # mpeltonen/sbt-idea plugin
40 | .idea_modules/
41 |
42 | # JIRA plugin
43 | atlassian-ide-plugin.xml
44 |
45 | # Crashlytics plugin (for Android Studio and IntelliJ)
46 | com_crashlytics_export_strings.xml
47 | crashlytics.properties
48 | crashlytics-build.properties
49 |
50 |
51 | ### Java template
52 | *.class
53 |
54 | # Mobile Tools for Java (J2ME)
55 | .mtj.tmp/
56 |
57 | # Package Files #
58 | *.jar
59 | *.war
60 | *.ear
61 |
62 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
63 | hs_err_pid*
64 |
65 |
66 |
--------------------------------------------------------------------------------
/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | org.sssta.androidtools
3 | Android Tools
4 | 1.2
5 | SSSTA
6 |
7 |
9 | Supporting:
10 | 1.generate Show Toast by toast postfix
11 | 2.generate findViewById according layout
12 | ]]>
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 | com.intellij.modules.lang
26 | org.jetbrains.android
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/demo/clk.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cauchywei/AndroidToolsPlugin/b400765045aa81483f4a2af577003dce1d9d700e/demo/clk.gif
--------------------------------------------------------------------------------
/demo/findViewById.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cauchywei/AndroidToolsPlugin/b400765045aa81483f4a2af577003dce1d9d700e/demo/findViewById.gif
--------------------------------------------------------------------------------
/demo/inflateFindViewById.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cauchywei/AndroidToolsPlugin/b400765045aa81483f4a2af577003dce1d9d700e/demo/inflateFindViewById.gif
--------------------------------------------------------------------------------
/demo/toast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cauchywei/AndroidToolsPlugin/b400765045aa81483f4a2af577003dce1d9d700e/demo/toast.gif
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | #Android Tools ( developing... )
2 | ====================
3 |
4 |
5 | Android Tools is an Android Studio plugin helping developer reducing heavy and repeat works in development.
6 |
7 |
8 |
9 | ####DEMO
10 |
11 | 
12 | 
13 | 
14 | 
15 |
16 |
17 |
18 | ####**Postfix Extension**
19 |
20 | Postfix |Comment
21 | ----------|---------
22 | `.toast` |generate `Toast.makeText(this, msg,Toast.LENGTH_SHORT).show();`
23 | `.clk` |generate `view.onClickListener(listener);` according to context
24 | ... |
25 |
26 |
27 |
28 | ####**FindViewById Generating**
29 | You can generate two type `findViewById` from `setContentView(R.layout.activity_login)` statement or `layoutInflater.inflate(R.layout.activity_login,parent)` statement
30 |
31 | * **local** :e.g. generating `TextView usernameTextView = (TextView)findViewById(R.id.textView_username)`
32 | * **field** :e.g. generating `private TextView mUsernameTextView;` and
33 | `mUsernameTextView = (TextView)findViewById(R.id.textView_username);` two parts
34 |
35 | ######**Naming Rule**
36 | * **Activity/Fragment**: `_` e.g. `LoginActivity`
37 | * **field**: `m__` e.g. `mUsernameTextView`
38 | * **local var**: `_` e.g. `usernameTextView`
39 | * **resourse id**: `__` e.g. `textView_login_username`
40 |
41 | Assuming we have an Activity named `LoginActivity` whose's layout file named `activiy_login.xml`
42 |
43 | A TextView in `activity_layout.xml`
44 |
45 | ```xml
46 |
50 | ```
51 |
52 | ######**The process of name-converting is as follow**
53 |
54 | 1. Remove the type prefix of view id according to tag name
55 | * `textView_login_username` -> `login_username`
56 | * abbreviattional prefix also works e.g. `tv_login_username` -> `login_username`
57 | 2. Remove the module according to layout file (`activity_login.xml`) name
58 | * `login_username` -> `username`
59 | * abbreviattional prefix also works e.g. `lgn_username` -> `username`
60 |
61 | 3. Append ClassName according to tag name
62 | * `username` -> `usernameTextView` or `mUsernameTextView`
63 |
64 |
65 | finally. `textView_login_username` -> `usernameTextView` or `mUsernameTextView`
66 |
67 | Next Plan
68 | ----------
69 | ####**Generate Adapter and Model according item layout**
70 |
71 |
72 | License
73 | -----------
74 | ```
75 | Copyright (c) 2015 [cauchywei@gmail.com]
76 |
77 | Licensed under the Apache License, Version 2.0 (the "License”);
78 | you may not use this file except in compliance with the License.
79 | You may obtain a copy of the License at
80 |
81 | http://www.apache.org/licenses/LICENSE-2.0
82 |
83 | Unless required by applicable law or agreed to in writing, software
84 | distributed under the License is distributed on an "AS IS" BASIS,
85 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
86 | See the License for the specific language governing permissions and
87 | limitations under the License.
88 | ```
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/action/AbstractFvbiGeneratorAction.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.action;
2 |
3 | import com.intellij.codeInsight.CodeInsightActionHandler;
4 | import com.intellij.codeInsight.generation.actions.BaseGenerateAction;
5 | import com.intellij.openapi.editor.Editor;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.openapi.util.TextRange;
8 | import com.intellij.patterns.PlatformPatterns;
9 | import com.intellij.psi.*;
10 | import com.intellij.psi.util.PsiTreeUtil;
11 | import com.intellij.psi.util.PsiUtilBase;
12 | import org.jetbrains.annotations.NotNull;
13 | import org.sssta.androidtools.util.LayoutUtil;
14 |
15 | /**
16 | * Created by cauchywei on 15/8/17.
17 | */
18 | public abstract class AbstractFvbiGeneratorAction extends BaseGenerateAction {
19 |
20 | public AbstractFvbiGeneratorAction() {
21 | super(null);
22 | }
23 |
24 | public AbstractFvbiGeneratorAction(CodeInsightActionHandler handler) {
25 | super(handler);
26 | }
27 |
28 |
29 | @Override
30 | protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
31 |
32 | int offset = editor.getCaretModel().getOffset();
33 | PsiElement psiElement = file.findElementAt(offset);
34 |
35 | if(!PlatformPatterns.psiElement().inside(PsiMethodCallExpression.class).accepts(psiElement)) {
36 | return false;
37 | }
38 |
39 | PsiMethodCallExpression psiMethodCallExpression = PsiTreeUtil.getParentOfType(psiElement, PsiMethodCallExpression.class);
40 | if(psiMethodCallExpression == null) {
41 | return false;
42 | }
43 |
44 | PsiMethod psiMethod = psiMethodCallExpression.resolveMethod();
45 | if(psiMethod == null) {
46 | return false;
47 | }
48 |
49 | if (psiMethod.getName().equals("setContentView") || psiMethod.getName().equals("inflate")){
50 | return true;
51 | }
52 |
53 | return super.isValidForFile(project, editor, file);
54 | }
55 |
56 | @Override
57 | public void actionPerformedImpl(@NotNull Project project,@NotNull Editor editor) {
58 |
59 | PsiFile layoutXmlFile = LayoutUtil.findLayoutXmlFile(project, editor);
60 | generate(project,editor,layoutXmlFile);
61 |
62 | }
63 |
64 | public void generate(@NotNull Project project, @NotNull Editor editor, PsiFile xmlFile) {
65 |
66 | final PsiElement context = PsiUtilBase.getElementAtCaret(editor);
67 |
68 |
69 | PsiMethodCallExpression callExpression = PsiTreeUtil.getParentOfType(context,PsiMethodCallExpression.class);
70 |
71 | boolean found = false;
72 | PsiElement psiElement = callExpression;
73 |
74 | while(psiElement.getNextSibling()!=null){
75 | psiElement = psiElement.getNextSibling();
76 | if (psiElement.getText().trim().equals(";")){
77 | found = true;
78 | break;
79 | }else if (!psiElement.getText().trim().equals("")){
80 | break;
81 | }
82 | }
83 |
84 | TextRange textRange = psiElement.getTextRange();
85 | editor.getCaretModel().moveToOffset(found?textRange.getEndOffset():textRange.getStartOffset());
86 |
87 |
88 | insertStatement(project,editor,context,xmlFile);
89 |
90 | }
91 |
92 | public abstract void insertStatement(@NotNull Project project,@NotNull Editor editor,PsiElement context,PsiFile xmlFile);
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/action/FieldsFvbiGeneratorAction.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.action;
2 |
3 | import com.intellij.codeInsight.template.Template;
4 | import com.intellij.codeInsight.template.TemplateEditingAdapter;
5 | import com.intellij.codeInsight.template.TemplateManager;
6 | import com.intellij.openapi.actionSystem.ActionManager;
7 | import com.intellij.openapi.actionSystem.ActionPlaces;
8 | import com.intellij.openapi.actionSystem.AnAction;
9 | import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
10 | import com.intellij.openapi.command.WriteCommandAction;
11 | import com.intellij.openapi.editor.Editor;
12 | import com.intellij.openapi.project.Project;
13 | import com.intellij.openapi.ui.playback.commands.ActionCommand;
14 | import com.intellij.psi.*;
15 | import com.intellij.psi.util.PsiTreeUtil;
16 | import org.jetbrains.annotations.NotNull;
17 | import org.sssta.androidtools.action.generator.FieldsFvbiGenerator;
18 |
19 | import java.util.List;
20 |
21 | /**
22 | * Created by cauchywei on 15/8/17.
23 | */
24 | public class FieldsFvbiGeneratorAction extends AbstractFvbiGeneratorAction {
25 |
26 | FieldsFvbiGenerator fieldsFvbiGenerator = new FieldsFvbiGenerator();
27 |
28 |
29 | @Override
30 | public void insertStatement(@NotNull Project project,@NotNull Editor editor,PsiElement context,PsiFile xmlFile) {
31 |
32 | String viewParentName = null;
33 | PsiLocalVariable variable = PsiTreeUtil.getParentOfType(context, PsiLocalVariable.class);
34 | if (variable != null) {
35 | viewParentName = variable.getName();
36 | }
37 |
38 | Template template = fieldsFvbiGenerator.generator(project, editor, xmlFile, viewParentName);
39 | final List psiFields = fieldsFvbiGenerator.generatorFields(project, editor, xmlFile,context);
40 | final PsiClass psiClass = PsiTreeUtil.getTopmostParentOfType(context,PsiClass.class);
41 |
42 | WriteCommandAction.runWriteCommandAction(project, new Runnable() {
43 | @Override
44 | public void run() {
45 | PsiField[] fields = psiClass.getFields();
46 |
47 | for (PsiField newField : psiFields) {
48 | boolean exist = false;
49 | for (PsiField field : fields) {
50 | if (field.getName().equals(newField.getName())) {
51 | exist = true;
52 | break;
53 | }
54 | }
55 |
56 | if (!exist) {
57 | psiClass.add(newField);
58 | }
59 | }
60 | }
61 | });
62 |
63 |
64 | TemplateManager.getInstance(project).startTemplate(editor, template, new TemplateEditingAdapter() {
65 | @Override
66 | public void templateFinished(Template template, boolean brokenOff) {
67 |
68 |
69 |
70 |
71 | // // format and add ;
72 | final ActionManager actionManager = ActionManagerImpl.getInstance();
73 | final String editorCompleteStatementText = "EditorCompleteStatement";
74 | final AnAction action = actionManager.getAction(editorCompleteStatementText);
75 | actionManager.tryToExecute(action, ActionCommand.getInputEvent(editorCompleteStatementText), null, ActionPlaces.UNKNOWN, true);
76 | }
77 | });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/action/LocalFvbiGeneratorAction.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.action;
2 |
3 | import com.intellij.codeInsight.template.Template;
4 | import com.intellij.codeInsight.template.TemplateEditingAdapter;
5 | import com.intellij.codeInsight.template.TemplateManager;
6 | import com.intellij.openapi.actionSystem.ActionManager;
7 | import com.intellij.openapi.actionSystem.ActionPlaces;
8 | import com.intellij.openapi.actionSystem.AnAction;
9 | import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
10 | import com.intellij.openapi.editor.Editor;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.openapi.ui.playback.commands.ActionCommand;
13 | import com.intellij.psi.PsiElement;
14 | import com.intellij.psi.PsiFile;
15 | import com.intellij.psi.PsiLocalVariable;
16 | import com.intellij.psi.util.PsiTreeUtil;
17 | import org.jetbrains.annotations.NotNull;
18 | import org.sssta.androidtools.action.generator.LocalFvbiGenerator;
19 |
20 | /**
21 | * Created by cauchywei on 15/8/17.
22 | */
23 | public class LocalFvbiGeneratorAction extends AbstractFvbiGeneratorAction {
24 |
25 | LocalFvbiGenerator localFvbiGenerator = new LocalFvbiGenerator();
26 |
27 |
28 | @Override
29 | public void insertStatement(@NotNull Project project,@NotNull Editor editor,PsiElement context,PsiFile xmlFile) {
30 |
31 | String viewParentName = null;
32 | PsiLocalVariable variable = PsiTreeUtil.getParentOfType(context, PsiLocalVariable.class);
33 | if (variable != null) {
34 | viewParentName = variable.getName();
35 | }
36 |
37 | Template template = localFvbiGenerator.generator(project, editor, xmlFile, viewParentName);
38 |
39 | TemplateManager.getInstance(project).startTemplate(editor, template, new TemplateEditingAdapter() {
40 | @Override
41 | public void templateFinished(Template template, boolean brokenOff) {
42 | // // format and add ;
43 | final ActionManager actionManager = ActionManagerImpl.getInstance();
44 | final String editorCompleteStatementText = "EditorCompleteStatement";
45 | final AnAction action = actionManager.getAction(editorCompleteStatementText);
46 | actionManager.tryToExecute(action, ActionCommand.getInputEvent(editorCompleteStatementText), null, ActionPlaces.UNKNOWN, true);
47 | }
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/action/generator/AbstractFvbiGenerator.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.action.generator;
2 |
3 | import com.intellij.codeInsight.template.Template;
4 | import com.intellij.codeInsight.template.TemplateManager;
5 | import com.intellij.openapi.editor.Editor;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.psi.PsiFile;
8 | import org.sssta.androidtools.model.ViewModel;
9 | import org.sssta.androidtools.util.LayoutUtil;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * Created by cauchywei on 15/8/17.
15 | */
16 | public abstract class AbstractFvbiGenerator {
17 |
18 | public abstract String getFvbiFormat();
19 |
20 | public abstract String getFvbiStatementTemplate(String format, ViewModel view,String viewParentName);
21 |
22 | public String processTemplateString(String template){
23 | return template;
24 | }
25 |
26 | public Template generateTemplate(Project project ,String template){
27 | TemplateManager templateManager = TemplateManager.getInstance(project);
28 | return templateManager.createTemplate("", "",processTemplateString(template));
29 | }
30 |
31 | public Template generator(Project project, Editor editor, PsiFile xmlFile, String viewParentName) {
32 |
33 |
34 | List views = LayoutUtil.getContainingIdViewsInXml(xmlFile);
35 | viewParentName = viewParentName == null?"":viewParentName + ".";
36 |
37 | String statementFormat = getFvbiFormat();
38 | StringBuilder templateString = new StringBuilder("\n");
39 |
40 | for (ViewModel view:views){
41 | String fvbiStatement = getFvbiStatementTemplate(statementFormat,view,viewParentName);
42 | templateString.append(fvbiStatement);
43 | }
44 |
45 | templateString.append("$end$");
46 |
47 | return generateTemplate(project,templateString.toString());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/action/generator/FieldsFvbiGenerator.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.action.generator;
2 |
3 | import com.intellij.openapi.editor.Editor;
4 | import com.intellij.openapi.project.Project;
5 | import com.intellij.psi.*;
6 | import com.intellij.psi.util.PsiTreeUtil;
7 | import org.sssta.androidtools.model.ViewModel;
8 | import org.sssta.androidtools.util.LayoutUtil;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by cauchywei on 15/8/18.
15 | */
16 | public class FieldsFvbiGenerator extends AbstractFvbiGenerator {
17 |
18 | @Override
19 | public String getFvbiFormat() {
20 | return "%s = (%s) %sfindViewById(%s);\n";
21 | }
22 |
23 | @Override
24 | public String getFvbiStatementTemplate(String statementFormat,ViewModel view, String viewParentName) {
25 | String varName = view.getFieldName();
26 | String clazz = view.getFqClazz();
27 | String fqId = view.getFqId();
28 | return String.format(statementFormat,varName,clazz,viewParentName,fqId);
29 | }
30 |
31 | public List generatorFields(Project project, Editor editor, PsiFile xmlFile,PsiElement context) {
32 |
33 | List views = LayoutUtil.getContainingIdViewsInXml(xmlFile);
34 | PsiClass psiClass = PsiTreeUtil.getParentOfType(context, PsiClass.class);
35 | String fieldFormat = "private %s %s;\n";
36 | List fieldStatements = new ArrayList<>();
37 | PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
38 |
39 | for (ViewModel view:views){
40 | String fvbiStatement = String.format(fieldFormat,view.getClazz(),view.getFieldName());
41 | fieldStatements.add(factory.createFieldFromText(fvbiStatement, psiClass));
42 | }
43 |
44 | return fieldStatements;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/action/generator/LocalFvbiGenerator.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.action.generator;
2 |
3 | import org.sssta.androidtools.model.ViewModel;
4 |
5 | /**
6 | * Created by cauchywei on 15/8/17.
7 | */
8 | public class LocalFvbiGenerator extends AbstractFvbiGenerator {
9 |
10 | @Override
11 | public String getFvbiFormat() {
12 | return "%s %s = (%s) %sfindViewById(%s);\n";
13 | }
14 |
15 | @Override
16 | public String getFvbiStatementTemplate(String statementFormat,ViewModel view, String viewParentName) {
17 | String varName = view.getLocalVarName();
18 | String clazz = view.getFqClazz();
19 | return String.format(statementFormat,view.getClazz(),varName,clazz,viewParentName,view.getFqId());
20 | }
21 |
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/model/ViewModel.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.model;
2 |
3 | import com.intellij.openapi.util.text.StringUtil;
4 | import org.sssta.androidtools.util.CommonUtil;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.Collections;
9 | import java.util.List;
10 |
11 | /**
12 | * Created by cauchywei on 15/8/17.
13 | */
14 | public class ViewModel {
15 |
16 | private String id;
17 | private String fqId;
18 |
19 | private String clazz;
20 | private String fqClazz;
21 |
22 | private String from;
23 |
24 |
25 | public ViewModel(String from,String id, String fqClazz) {
26 | setFrom(from);
27 | setId(id);
28 | setFqClazz(fqClazz);
29 | }
30 |
31 | public String getId() {
32 | return id;
33 | }
34 |
35 | public void setId(String id) {
36 |
37 | if (id.matches("^@\\+?id/.*")){
38 | this.id = id.substring(id.indexOf('/')+1,id.length());
39 | }
40 |
41 | if (this.id.contains(".")){
42 | this.id = this.id.substring(this.id.lastIndexOf('.'),this.id.length());
43 | }
44 |
45 | setFqId("R.id."+this.id);
46 | }
47 |
48 | public String getClazz() {
49 | return clazz;
50 | }
51 |
52 | public void setClazz(String clazz) {
53 | this.clazz = clazz;
54 | }
55 |
56 | public String getFrom() {
57 | return from;
58 | }
59 |
60 | public void setFrom(String from) {
61 | if (from.contains(".")){
62 | this.from = from.split("\\.")[0];
63 | }
64 | this.from = from;
65 | }
66 |
67 | public String getFqId() {
68 | return fqId;
69 | }
70 |
71 | public void setFqId(String fqId) {
72 | this.fqId = fqId;
73 | }
74 |
75 | public String getFqClazz() {
76 | return fqClazz;
77 | }
78 |
79 | public void setFqClazz(String fqClazz) {
80 |
81 | if (fqClazz.contains(".")) {
82 | this.fqClazz = fqClazz;
83 | }else if ((fqClazz.equals("View")) || (fqClazz.equals("ViewGroup"))) {
84 | this.fqClazz = "android.view." + fqClazz;
85 | }else {
86 | this.fqClazz = "android.widget." + fqClazz;
87 | }
88 |
89 | String[] split = fqClazz.split("\\.");
90 | setClazz(split[split.length - 1]);
91 | }
92 |
93 | public String getLocalVarName(){
94 | // List clazzNames = CommonUtil.splitCamelName(getClazz());
95 | String clazzName = getClazz();
96 | List splitIds = CommonUtil.splitUnderscoreName(getId());
97 | List splitFrom = CommonUtil.splitUnderscoreName(getFrom());
98 | List result;
99 |
100 | if (splitIds.isEmpty())
101 | return getClazz();
102 |
103 | do {
104 |
105 | //remove the type prefix e.g. activity_login -> login
106 | if (!splitFrom.isEmpty()) {
107 | if (CommonUtil.nameMatch(splitFrom.get(0), "activity") || CommonUtil.nameMatch(splitFrom.get(0), "fragment") || CommonUtil.nameMatch(splitFrom.get(0), "item")) {
108 | splitFrom.remove(0);
109 | }
110 | }
111 |
112 | result = new ArrayList<>(Arrays.asList(new String[splitIds.size()]));
113 | Collections.copy(result,splitIds);
114 |
115 | //remove the type prefix e.g TextView: textView_login_user_name -> login_user_name
116 | if (CommonUtil.nameMatch(clazzName.toLowerCase(),splitIds.get(0).toLowerCase())){
117 | splitIds.remove(0);
118 | }
119 | // for (String clazzName : clazzNames) {
120 | // if (CommonUtil.nameMatch(splitIds.get(0), clazzName)) {
121 | // splitIds.remove(0);
122 | // }
123 | // }
124 |
125 |
126 | if (splitIds.isEmpty()){
127 | break;
128 | }
129 |
130 | //remove scope name e.g. login_user_name (from activity_login) -> user_name
131 | int minLen = Math.min(splitFrom.size(), splitIds.size());
132 | result = new ArrayList<>(Arrays.asList(new String[splitIds.size()]));
133 | Collections.copy(result,splitIds);
134 |
135 | for (int i = 0; i < minLen; i++) {
136 | if (CommonUtil.nameMatch(splitFrom.get(0), splitIds.get(0))) {
137 | splitIds.remove(0);
138 | } else {
139 | break;
140 | }
141 | }
142 |
143 | if (splitIds.isEmpty()){
144 | break;
145 | }else {
146 | result = splitIds;
147 | }
148 |
149 |
150 | }while (false);
151 |
152 | StringBuilder stringBuilder = new StringBuilder();
153 | for (String s:result) {
154 | stringBuilder.append(StringUtil.capitalize(s));
155 | }
156 |
157 | stringBuilder.append(getClazz());
158 |
159 | return StringUtil.decapitalize(stringBuilder.toString());
160 |
161 | }
162 |
163 | public String getFieldName(){
164 | return "m" + StringUtil.capitalize(getLocalVarName());
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/AndroidPostfixTemplateProvider.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.posfix;
2 |
3 | import com.intellij.codeInsight.template.postfix.templates.JavaPostfixTemplateProvider;
4 | import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate;
5 | import com.intellij.util.containers.ContainerUtil;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.sssta.androidtools.posfix.template.ClickListenerPostfixTemplate;
8 | import org.sssta.androidtools.posfix.template.ToastPostfixTemplate;
9 |
10 | import java.util.HashSet;
11 | import java.util.Set;
12 |
13 | /**
14 | * Created by cauchywei on 15/8/15.
15 | */
16 | public class AndroidPostfixTemplateProvider extends JavaPostfixTemplateProvider {
17 | private final HashSet templates;
18 |
19 | public AndroidPostfixTemplateProvider() {
20 | templates = ContainerUtil.newHashSet(
21 | new ToastPostfixTemplate(),
22 | new ClickListenerPostfixTemplate()
23 | );
24 | }
25 |
26 | @NotNull
27 | @Override
28 | public Set getTemplates() {
29 | return templates;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/internal/AbstractRichStringBasedPostfixTemplate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Bob Browning
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.sssta.androidtools.posfix.internal;
17 |
18 | import com.intellij.codeInsight.template.Template;
19 | import com.intellij.codeInsight.template.TemplateEditingAdapter;
20 | import com.intellij.codeInsight.template.TemplateManager;
21 | import com.intellij.codeInsight.template.impl.TextExpression;
22 | import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelector;
23 | import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateWithExpressionSelector;
24 | import com.intellij.codeInsight.template.postfix.templates.PostfixTemplatesUtils;
25 | import com.intellij.openapi.actionSystem.ActionManager;
26 | import com.intellij.openapi.actionSystem.ActionPlaces;
27 | import com.intellij.openapi.actionSystem.AnAction;
28 | import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
29 | import com.intellij.openapi.editor.Document;
30 | import com.intellij.openapi.editor.Editor;
31 | import com.intellij.openapi.project.Project;
32 | import com.intellij.openapi.ui.playback.commands.ActionCommand;
33 | import com.intellij.psi.PsiElement;
34 | import org.jetbrains.annotations.NotNull;
35 | import org.jetbrains.annotations.Nullable;
36 | import org.sssta.androidtools.util.AndroidPostfixTemplatesUtils;
37 |
38 | /**
39 | * Base class that is a modified form of {@link com.intellij.codeInsight.template.postfix.templates.StringBasedPostfixTemplate} that passes the project to {@link
40 | * AbstractRichStringBasedPostfixTemplate#createTemplate} to allow querying of project properties.
41 | *
42 | * @author Bob Browning
43 | */
44 | public abstract class AbstractRichStringBasedPostfixTemplate extends PostfixTemplateWithExpressionSelector {
45 |
46 | protected AbstractRichStringBasedPostfixTemplate(@NotNull String name,
47 | @NotNull String example,
48 | @NotNull PostfixTemplateExpressionSelector selector) {
49 | super(name, example, selector);
50 | }
51 |
52 | @Override
53 | protected final void expandForChooseExpression(@NotNull PsiElement expr, @NotNull final Editor editor) {
54 | Project project = expr.getProject();
55 | Document document = editor.getDocument();
56 | PsiElement elementForRemoving = shouldRemoveParent() ? expr.getParent() : expr;
57 | document.deleteString(elementForRemoving.getTextRange().getStartOffset(),
58 | elementForRemoving.getTextRange().getEndOffset());
59 | TemplateManager manager = TemplateManager.getInstance(project);
60 |
61 | String templateString = getTemplateString(expr);
62 | if (templateString == null) {
63 | PostfixTemplatesUtils.showErrorHint(expr.getProject(), editor);
64 | return;
65 | }
66 |
67 | Template template = createTemplate(project, manager, templateString);
68 |
69 | if (shouldAddExpressionToContext()) {
70 | addExprVariable(expr, template);
71 | }
72 |
73 | setVariables(template, expr);
74 | manager.startTemplate(editor, template, new TemplateEditingAdapter() {
75 | @Override
76 | public void templateFinished(Template template, boolean brokenOff) {
77 | // format and add ;
78 | final ActionManager actionManager = ActionManagerImpl.getInstance();
79 | final String editorCompleteStatementText = "EditorCompleteStatement";
80 | final AnAction action = actionManager.getAction(editorCompleteStatementText);
81 | actionManager.tryToExecute(action, ActionCommand.getInputEvent(editorCompleteStatementText), null, ActionPlaces.UNKNOWN, true);
82 | }
83 | });
84 | }
85 |
86 | protected void addExprVariable(@NotNull PsiElement expr, Template template) {
87 | template.addVariable("expr", new TextExpression(expr.getText()), false);
88 | }
89 |
90 | /**
91 | * Add custom variables to the template.
92 | *
93 | * @param template The template
94 | * @param element The expression being replaced
95 | */
96 | protected void setVariables(@NotNull Template template, @NotNull PsiElement element) {
97 | }
98 |
99 | @Nullable
100 | public abstract String getTemplateString(@NotNull PsiElement element);
101 |
102 | /**
103 | * Returns true if the {@code expr} variable should be added to the template by default.
104 | */
105 | protected boolean shouldAddExpressionToContext() {
106 | return true;
107 | }
108 |
109 | /**
110 | * Returns true if the formatting manager should be applied to the generated code block.
111 | */
112 | protected boolean shouldReformat() {
113 | return true;
114 | }
115 |
116 | /**
117 | * Returns true if the parent element should be removed, for example for topmost expression.
118 | */
119 | protected abstract boolean shouldRemoveParent();
120 |
121 | /**
122 | * Create a new instance of a code template for the current postfix template.
123 | *
124 | * @param project The current project
125 | * @param manager The template manager
126 | * @param templateString The template string
127 | */
128 | protected Template createTemplate(Project project, TemplateManager manager, String templateString) {
129 | Template template = manager.createTemplate("", "", templateString);
130 | template.setToReformat(shouldReformat());
131 | template.setValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE, false);
132 | return template;
133 | }
134 |
135 |
136 | /**
137 | * Gets the static method prefix for the android static method.
138 | *
139 | * @param className The android class name
140 | * @param methodName The method name
141 | * @param context The context element
142 | */
143 | protected String getStaticMethodPrefix(@NotNull String className,
144 | @NotNull String methodName,
145 | @NotNull PsiElement context) {
146 | return AndroidPostfixTemplatesUtils.getStaticMethodPrefix(className, methodName, context);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/internal/RichChooserStringBasedPostfixTemplate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Bob Browning
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.sssta.androidtools.posfix.internal;
17 |
18 | import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
19 | import com.intellij.openapi.util.Condition;
20 | import com.intellij.psi.PsiElement;
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | /**
24 | * @author Bob Browning
25 | */
26 | public abstract class RichChooserStringBasedPostfixTemplate extends AbstractRichStringBasedPostfixTemplate {
27 |
28 | protected RichChooserStringBasedPostfixTemplate(@NotNull String name,
29 | @NotNull String example,
30 | @NotNull Condition typeChecker) {
31 | super(name, example, JavaPostfixTemplatesUtils.selectorAllExpressionsWithCurrentOffset(typeChecker));
32 | }
33 |
34 | @Override
35 | protected boolean shouldRemoveParent() {
36 | return false;
37 | }
38 |
39 | @Override
40 | protected boolean shouldReformat() {
41 | return false;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/internal/RichTopmostStringBasedPostfixTemplate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Bob Browning
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.sssta.androidtools.posfix.internal;
17 |
18 | import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
19 | import com.intellij.openapi.util.Condition;
20 | import com.intellij.psi.PsiElement;
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | /**
24 | * @author Bob Browning
25 | */
26 | public abstract class RichTopmostStringBasedPostfixTemplate extends AbstractRichStringBasedPostfixTemplate {
27 |
28 | protected RichTopmostStringBasedPostfixTemplate(@NotNull String name,
29 | @NotNull String example,
30 | @NotNull Condition typeChecker) {
31 | super(name, example, JavaPostfixTemplatesUtils.selectorTopmost(typeChecker));
32 | }
33 |
34 | @Override
35 | protected boolean shouldRemoveParent() {
36 | return true;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/macro/TagMacro.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.posfix.macro;
2 |
3 | import com.intellij.codeInsight.template.*;
4 | import com.intellij.codeInsight.template.macro.ClassNameMacro;
5 | import com.intellij.codeInsight.template.macro.MacroUtil;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.psi.*;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | /**
11 | * macro for android log TAG parameter.
12 | *
13 | * @author takahirom
14 | */
15 | public class TagMacro extends Macro {
16 |
17 |
18 | public String getName() {
19 | return "tag";
20 | }
21 |
22 | public String getPresentableName() {
23 | return "tag";
24 | }
25 |
26 | @Nullable
27 | @Override
28 | public Result calculateResult(Expression[] expressions, ExpressionContext context) {
29 | if (isContainTagField(context)) {
30 | return new TextResult("TAG");
31 | } else {
32 | String className = new ClassNameMacro().calculateResult(new Expression[]{}, context).toString();
33 | if (className.length() > 23) {
34 | className = className.substring(0, 23);
35 | }
36 | return new TextResult("\"" + className + "\"");
37 | }
38 | }
39 |
40 | public boolean isAcceptableInContext(TemplateContextType context) {
41 | return context instanceof JavaCodeContextType;
42 | }
43 |
44 |
45 | public boolean isContainTagField(ExpressionContext context) {
46 | Project project = context.getProject();
47 | int offset = context.getStartOffset();
48 | PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(context.getEditor().getDocument());
49 | PsiElement place = file.findElementAt(offset);
50 | PsiVariable[] variables = MacroUtil.getVariablesVisibleAt(place, "");
51 | for (PsiVariable variable : variables) {
52 | if (variable instanceof PsiField && variable.hasModifierProperty("static")) {
53 | PsiField psiField = (PsiField) variable;
54 | if ("TAG".equals(psiField.getName())) {
55 | return true;
56 | }
57 | }
58 |
59 | }
60 |
61 | return false;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/macro/ViewListenerMacro.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.posfix.macro;
2 |
3 | import com.intellij.codeInsight.template.*;
4 | import com.intellij.psi.JavaPsiFacade;
5 | import com.intellij.psi.PsiElementFactory;
6 | import com.intellij.psi.PsiStatement;
7 | import com.intellij.psi.codeStyle.CodeStyleManager;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | /**
11 | * Created by cauchywei on 15/8/25.
12 | */
13 | public class ViewListenerMacro extends Macro {
14 | @Override
15 | public String getName() {
16 | return "new click listener";
17 | }
18 |
19 | @Override
20 | public String getPresentableName() {
21 | return "new click listener";
22 | }
23 |
24 | @Nullable
25 | @Override
26 | public Result calculateResult(Expression[] expressions, ExpressionContext expressionContext) {
27 |
28 | PsiElementFactory factory = JavaPsiFacade.getInstance(expressionContext.getProject()).getElementFactory();
29 | String listener = "new View.OnClickListener() {\n\t@Override\npublic void onClick(View v) {\n\n}\n}";
30 | PsiStatement statementFromText = factory.createStatementFromText(listener, null);
31 | statementFromText = (PsiStatement) CodeStyleManager.getInstance(expressionContext.getProject()).reformat(statementFromText);
32 | return new JavaPsiElementResult(statementFromText);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/template/ClickListenerPostfixTemplate.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.posfix.template;
2 |
3 | import com.intellij.codeInsight.template.Template;
4 | import com.intellij.codeInsight.template.impl.ConstantNode;
5 | import com.intellij.codeInsight.template.impl.MacroCallNode;
6 | import com.intellij.codeInsight.template.macro.VariableOfTypeMacro;
7 | import com.intellij.codeInsight.template.postfix.templates.StringBasedPostfixTemplate;
8 | import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
9 | import com.intellij.psi.PsiClass;
10 | import com.intellij.psi.PsiClassType;
11 | import com.intellij.psi.PsiElement;
12 | import com.intellij.psi.PsiField;
13 | import com.intellij.psi.util.InheritanceUtil;
14 | import com.intellij.psi.util.PsiTreeUtil;
15 | import org.jetbrains.annotations.NotNull;
16 | import org.jetbrains.annotations.Nullable;
17 | import org.sssta.androidtools.util.AndroidFQClass;
18 | import org.sssta.androidtools.util.AndroidPostfixTemplatesUtils;
19 |
20 | /**
21 | * Created by cauchywei on 15/8/25.
22 | */
23 | public class ClickListenerPostfixTemplate extends StringBasedPostfixTemplate {
24 |
25 | private boolean isNewListener = true;
26 |
27 | public ClickListenerPostfixTemplate() {
28 | super("clk", "view.setOnClickListener(listener)",JavaPostfixTemplatesUtils.selectorTopmost(AndroidPostfixTemplatesUtils.IS_VIEW));
29 | }
30 |
31 |
32 | @Nullable
33 | @Override
34 | public String getTemplateString(@NotNull PsiElement psiElement) {
35 |
36 | PsiClass topmostParentOfType = PsiTreeUtil.getTopmostParentOfType(psiElement, PsiClass.class);
37 |
38 | if (topmostParentOfType != null) {
39 | PsiClassType[] implementsListTypes = topmostParentOfType.getImplementsListTypes();
40 | for (PsiClassType implementsListType : implementsListTypes) {
41 | if (InheritanceUtil.isInheritor(implementsListType,AndroidFQClass.VIEW_ON_CLICK_LISTENER)){
42 | isNewListener = false;
43 | break;
44 | }
45 | }
46 |
47 | if (isNewListener){
48 | PsiField[] fields = topmostParentOfType.getFields();
49 | for (PsiField field : fields) {
50 | if (InheritanceUtil.isInheritor(field.getType(),AndroidFQClass.VIEW_ON_CLICK_LISTENER)) {
51 | isNewListener = false;
52 | break;
53 | }
54 | }
55 | }
56 | }
57 |
58 | String listener;
59 | if(isNewListener){
60 | listener = "new View.OnClickListener() {\n\t@Override\npublic void onClick(View v) {\n$END$\n}\n}";
61 | }else {
62 | listener = "$listener$";
63 | }
64 |
65 | return "$expr$.setOnClickListener(" + listener + ");";
66 |
67 | }
68 |
69 | @Override
70 | public void setVariables(@NotNull Template template, @NotNull PsiElement element) {
71 | super.setVariables(template, element);
72 | //old implement
73 | // MacroCallNode node = new MacroCallNode(new VariableOfTypeMacro());
74 | // node.addParameter(new ConstantNode(AndroidFQClass.VIEW_ON_CLICK_LISTENER));
75 | //
76 | // MacroCallNode defNode = new MacroCallNode(new ViewListenerMacro());
77 | //
78 | // template.addVariable("listener", node, defNode, true);
79 | if (!isNewListener){
80 | MacroCallNode node = new MacroCallNode(new VariableOfTypeMacro());
81 | node.addParameter(new ConstantNode(AndroidFQClass.VIEW_ON_CLICK_LISTENER));
82 | template.addVariable("listener", node, new ConstantNode(""), true);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/posfix/template/ToastPostfixTemplate.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.posfix.template;
2 |
3 | import com.intellij.codeInsight.template.Template;
4 | import com.intellij.codeInsight.template.impl.ConstantNode;
5 | import com.intellij.codeInsight.template.impl.MacroCallNode;
6 | import com.intellij.codeInsight.template.macro.VariableOfTypeMacro;
7 | import com.intellij.psi.PsiElement;
8 | import org.jetbrains.annotations.NotNull;
9 | import org.sssta.androidtools.posfix.internal.RichChooserStringBasedPostfixTemplate;
10 | import org.sssta.androidtools.util.AndroidFQClass;
11 | import org.sssta.androidtools.util.AndroidPostfixTemplatesUtils;
12 |
13 | /**
14 | * Created by cauchywei on 15/8/15.
15 | */
16 | public class ToastPostfixTemplate extends RichChooserStringBasedPostfixTemplate {
17 |
18 | public ToastPostfixTemplate() {
19 | this("toast");
20 | }
21 |
22 | public ToastPostfixTemplate(@NotNull String alias) {
23 | super(alias, "Toast.makeText(context, expr, Toast.LENGTH_SHORT).show();", AndroidPostfixTemplatesUtils.IS_NON_NULL);
24 | }
25 |
26 | @Override
27 | public String getTemplateString(@NotNull PsiElement element) {
28 | return getStaticMethodPrefix(AndroidFQClass.TOAST, "makeText", element) + "($context$, $expr$, Toast.LENGTH_SHORT).show()$END$";
29 | }
30 |
31 | @Override
32 | protected void setVariables(@NotNull Template template, @NotNull PsiElement element) {
33 |
34 | MacroCallNode node = new MacroCallNode(new VariableOfTypeMacro());
35 | node.addParameter(new ConstantNode(AndroidFQClass.CONTEXT));
36 | template.addVariable("context", node, new ConstantNode(""), false);
37 |
38 | }
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/util/AndroidFQClass.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.util;
2 |
3 | /**
4 | * Created by cauchywei on 15/8/16.
5 | */
6 | public class AndroidFQClass {
7 | public static final String CONTEXT = "android.content.Context";
8 | public static final String TOAST = "android.widget.Toast";
9 | public static final String VIEW = "android.view.View";
10 | public static final String VIEW_ON_CLICK_LISTENER = "android.view.View.OnClickListener";
11 |
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/util/AndroidPostfixTemplatesUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Bob Browning
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.sssta.androidtools.util;
17 |
18 | import com.intellij.codeInsight.NullableNotNullManager;
19 | import com.intellij.openapi.util.Condition;
20 | import com.intellij.psi.*;
21 | import com.intellij.psi.util.InheritanceUtil;
22 | import com.intellij.psi.util.PsiTreeUtil;
23 | import com.siyeh.ig.psiutils.ParenthesesUtils;
24 | import org.jetbrains.annotations.Contract;
25 | import org.jetbrains.annotations.NonNls;
26 | import org.jetbrains.annotations.NotNull;
27 | import org.jetbrains.annotations.Nullable;
28 |
29 | import static com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils.*;
30 |
31 | /**
32 | * Collection of helper methods for surrounding an expression with android preconditions.
33 | *
34 | * @author Bob Browning
35 | */
36 | public class AndroidPostfixTemplatesUtils {
37 |
38 | /**
39 | * Condition that returns true if the element is not null.
40 | */
41 |
42 | public static final Condition IS_NON_NULL = new Condition() {
43 | @Override
44 | public boolean value(PsiElement element) {
45 | return IS_NON_VOID.value(element) && !AndroidPostfixTemplatesUtils.isAnnotatedNullable(element);
46 | }
47 |
48 | };
49 |
50 | /**
51 | * Condition that return true if the element instanceof android.view.View
52 | */
53 | public static final Condition IS_VIEW = new Condition() {
54 | @Override
55 | public boolean value(PsiElement element) {
56 | if (element != null && element instanceof PsiExpression) {
57 | PsiType type = ((PsiExpression) element).getType();
58 | return InheritanceUtil.isInheritor(type,AndroidFQClass.VIEW);
59 | } else {
60 | return false;
61 | }
62 | }
63 |
64 | };
65 |
66 | /**
67 | * Condition that returns true if the element is a {@link CharSequence}.
68 | */
69 | public static final Condition IS_CHAR_SEQUENCE = new Condition() {
70 | @Override
71 | public boolean value(PsiElement element) {
72 | return element instanceof PsiExpression
73 | && isCharSequence(((PsiExpression) element).getType());
74 | }
75 | };
76 |
77 | /**
78 | * Condition that returns true if the element is a {@link java.util.Map}.
79 | */
80 | public static final Condition IS_MAP = new Condition() {
81 | @Override
82 | public boolean value(PsiElement element) {
83 | return element instanceof PsiExpression
84 | && InheritanceUtil.isInheritor(((PsiExpression) element).getType(), CommonClassNames.JAVA_UTIL_MAP);
85 | }
86 | };
87 |
88 | /**
89 | * Condition that returns true if the element is an iterable.
90 | */
91 | public static final Condition IS_ITERABLE = new Condition() {
92 | @Override
93 | public boolean value(PsiElement element) {
94 | if (element instanceof PsiExpression) {
95 | PsiType type = ((PsiExpression) element).getType();
96 | return isIterable(type);
97 | }
98 | return false;
99 | }
100 | };
101 |
102 | /**
103 | * Condition that returns true if the element is an iterable or an iterator or an array.
104 | */
105 | public static final Condition IS_ARRAY_OR_ITERABLE_OR_ITERATOR = new Condition() {
106 | @Override
107 | public boolean value(PsiElement element) {
108 | if (element instanceof PsiExpression) {
109 | PsiType type = ((PsiExpression) element).getType();
110 | return isIterator(type) || isArray(type) || isIterable(type);
111 | }
112 | return false;
113 | }
114 | };
115 |
116 | /**
117 | * Condition that returns true if the element is an iterable or an iterator or an object array or a collection.
118 | */
119 | public static final Condition IS_NON_PRIMITIVE_ARRAY_OR_ITERABLE_OR_ITERATOR =
120 | new Condition() {
121 | @Override
122 | public boolean value(PsiElement element) {
123 | if (element instanceof PsiExpression) {
124 | PsiType type = ((PsiExpression) element).getType();
125 | return isObjectArrayTypeExpression(type)
126 | || isIterable(type)
127 | || isIterator(type);
128 | }
129 | return false;
130 | }
131 | };
132 |
133 | @NonNls
134 | private static final String JAVA_LANG_CHAR_SEQUENCE = "java.lang.CharSequence";
135 |
136 | @Contract("null -> false")
137 | public static boolean isCollection(@Nullable PsiType type) {
138 | return type != null && InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_COLLECTION);
139 | }
140 |
141 | @Contract("null -> false")
142 | public static boolean isIterator(@Nullable PsiType type) {
143 | return type != null && InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_ITERATOR);
144 | }
145 |
146 |
147 | @Contract("null -> false")
148 | public static boolean isCharSequence(@Nullable PsiType type) {
149 | return type != null && InheritanceUtil.isInheritor(type, JAVA_LANG_CHAR_SEQUENCE);
150 | }
151 |
152 | @Contract("null -> false")
153 | public static boolean isObjectArrayTypeExpression(@Nullable PsiType type) {
154 | return type instanceof PsiArrayType &&
155 | !(((PsiArrayType) type).getComponentType() instanceof PsiPrimitiveType);
156 | }
157 |
158 | /**
159 | * Gets the expression for acquiring the size of the specified expression.
160 | *
161 | * @param expr The expression to evaluate
162 | */
163 | @Nullable
164 | public static String getExpressionNumberOrArrayOrIterableBound(@NotNull PsiExpression expr) {
165 | PsiType type = expr.getType();
166 | if (isNumber(type)) {
167 | return expr.getText();
168 | } else if (isArray(type)) {
169 | return expr.getText() + ".length";
170 | } else if (isCollection(type)) {
171 | return expr.getText() + ".size()";
172 | } else if (isIterable(type)) {
173 | return "com.google.common.collect.Iterables.size(" + expr.getText() + ")";
174 | }
175 | return null;
176 | }
177 |
178 | /**
179 | * Get the prefix required for the specified imported static method within the current context.
180 | *
181 | * @param fqClassName The qualified class name
182 | * @param methodName The method name
183 | * @param context The current context
184 | */
185 | public static String getStaticMethodPrefix(@NotNull String fqClassName, @NotNull String methodName,
186 | @NotNull PsiElement context) {
187 | return ImportUtil.hasImportStatic(fqClassName, methodName, context) ? methodName : (fqClassName + "." + methodName);
188 | }
189 |
190 | public static boolean isTopmostExpression(@NotNull PsiElement element) {
191 | return element.equals(getTopmostExpression(element));
192 | }
193 |
194 | @Contract("null -> false")
195 | public static boolean isSemicolonNeeded(PsiElement context) {
196 | PsiStatement statement = PsiTreeUtil.getParentOfType(context, PsiStatement.class);
197 | return statement != null && statement.getLastChild() instanceof PsiErrorElement;
198 | }
199 |
200 | public static boolean isAnnotatedNullable(PsiElement element) {
201 | PsiExpression expression;
202 | if (element instanceof PsiExpression) {
203 | expression = (PsiExpression) element;
204 | } else {
205 | expression = PsiTreeUtil.getParentOfType(element, PsiExpression.class, true);
206 | if (expression == null) {
207 | return false;
208 | }
209 | }
210 | expression = ParenthesesUtils.stripParentheses(expression);
211 | if (!(expression instanceof PsiReferenceExpression)) {
212 | return false;
213 | }
214 | final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) expression;
215 | final PsiElement target = referenceExpression.resolve();
216 | if (!(target instanceof PsiModifierListOwner)) {
217 | return false;
218 | }
219 | final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner) target;
220 | return NullableNotNullManager.isNullable(modifierListOwner);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/util/CommonUtil.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.util;
2 |
3 | import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.openapi.project.Project;
6 | import com.intellij.psi.*;
7 | import com.intellij.psi.util.InheritanceUtil;
8 | import com.intellij.refactoring.util.CommonRefactoringUtil;
9 | import org.jetbrains.annotations.Contract;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.util.LinkedList;
14 | import java.util.List;
15 |
16 | /**
17 | * Created by cauchywei on 15/8/16.
18 | */
19 | public class CommonUtil {
20 |
21 | public static void showErrorHint(Project project, Editor editor) {
22 | CommonRefactoringUtil.showErrorHint(project, editor, "Can't perform postfix completion", "Can't perform postfix completion", "");
23 | }
24 |
25 | public static void createSimpleStatement(@NotNull PsiElement context, @NotNull Editor editor, @NotNull String text) {
26 | PsiExpression expr = JavaPostfixTemplatesUtils.getTopmostExpression(context);
27 | PsiElement parent = expr != null ? expr.getParent() : null;
28 | assert parent instanceof PsiStatement;
29 | PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory();
30 | PsiStatement assertStatement = factory.createStatementFromText(text + " " + expr.getText() + ";", parent);
31 | PsiElement replace = parent.replace(assertStatement);
32 | editor.getCaretModel().moveToOffset(replace.getTextRange().getEndOffset());
33 | }
34 |
35 | @Contract("null -> false")
36 | public static boolean isIterable(@Nullable PsiType type) {
37 | return type != null && InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE);
38 | }
39 |
40 | @Contract("null -> false")
41 | public static boolean isArray(@Nullable PsiType type) {
42 | return type != null && type instanceof PsiArrayType;
43 | }
44 |
45 | @Contract("null -> false")
46 | public static boolean isBoolean(@Nullable PsiType type) {
47 | return type != null && (PsiType.BOOLEAN.equals(type) || PsiType.BOOLEAN.equals(PsiPrimitiveType.getUnboxedType(type)));
48 | }
49 |
50 | @Contract("null -> false")
51 | public static boolean isNumber(@Nullable PsiType type) {
52 | if (type == null) {
53 | return false;
54 | }
55 | if (PsiType.INT.equals(type) || PsiType.BYTE.equals(type) || PsiType.LONG.equals(type)) {
56 | return true;
57 | }
58 |
59 | PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(type);
60 | return PsiType.INT.equals(unboxedType) || PsiType.BYTE.equals(unboxedType) || PsiType.LONG.equals(unboxedType);
61 | }
62 |
63 | @Contract("null -> false")
64 | public static boolean isString(@Nullable PsiType type) {
65 | return type != null && type.equalsToText(CommonClassNames.JAVA_LANG_STRING);
66 | }
67 |
68 | // public static List splitViewId(String name){
69 | // if (name.contains("_")){
70 | // return splitUnderscoreName(name);
71 | // }else {
72 | // return splitCamelName(name);
73 | // }
74 | // }
75 |
76 | public static List splitCamelName(String name){
77 | List words = new LinkedList<>();
78 |
79 | int start = 0;
80 | for (int i = 0; i < name.length(); i++) {
81 | char ch = name.charAt(i);
82 | if (Character.isUpperCase(ch)){
83 | if (start != i){
84 | words.add(name.substring(start,i));
85 | }
86 | start = i;
87 | }
88 | }
89 |
90 | if (start < name.length()){
91 | words.add(name.substring(start,name.length()));
92 | }
93 |
94 | return words;
95 | }
96 |
97 | public static List splitUnderscoreName(final String name){
98 | return new LinkedList(){{
99 | String[] names = name.split("_");
100 | for (String name:names){
101 | add(name);
102 | }
103 | }};
104 | }
105 |
106 | /**
107 | * name match means t is s' common substring
108 | * @param s
109 | * @param t
110 | * @return
111 | */
112 | public static boolean nameMatch(@NotNull String s, @NotNull String t){
113 |
114 | if (s.length() == 0 || t.length() == 0)
115 | return false;
116 |
117 | t = t.toLowerCase();
118 | s = s.toLowerCase();
119 |
120 | int i, j = 0;
121 | int minLen;
122 |
123 | if (s.length() < t.length()){
124 | String tmp = s;
125 | s = t;
126 | t = tmp;
127 |
128 | }
129 |
130 | for (i = 0; i < s.length(); i++) {
131 | if (s.charAt(i) == t.charAt(j)){
132 | j++;
133 | if (j == t.length())
134 | return true;
135 | }
136 | }
137 |
138 | return false;
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/util/ImportUtil.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.util;
2 |
3 | import com.intellij.psi.*;
4 | import com.intellij.psi.search.GlobalSearchScope;
5 | import com.siyeh.ig.psiutils.ClassUtils;
6 |
7 | public class ImportUtil {
8 |
9 | /**
10 | * Check whether the current context has a static member import, either on-demand or explicit.
11 | *
12 | * @param fqClassName The class to import from
13 | * @param memberName The class member to import
14 | * @param context The context to be imported into
15 | */
16 | public static boolean hasImportStatic(String fqClassName, String memberName, PsiElement context) {
17 | final PsiFile file = context.getContainingFile();
18 | if (!(file instanceof PsiJavaFile)) {
19 | return false;
20 | }
21 | final PsiJavaFile javaFile = (PsiJavaFile) file;
22 | final PsiImportList importList = javaFile.getImportList();
23 | if (importList == null) {
24 | return false;
25 | }
26 | final PsiImportStaticStatement[] importStaticStatements = importList.getImportStaticStatements();
27 | for (PsiImportStaticStatement importStaticStatement : importStaticStatements) {
28 | if (importStaticStatement.isOnDemand()) {
29 | PsiClass psiClass = ClassUtils.findClass(fqClassName, context);
30 | if (psiClass != null && psiClass.equals(importStaticStatement.resolveTargetClass())) {
31 | return true;
32 | }
33 | continue;
34 | }
35 | final String name = importStaticStatement.getReferenceName();
36 | if (!memberName.equals(name)) {
37 | continue;
38 | }
39 | final PsiJavaCodeReferenceElement importReference = importStaticStatement.getImportReference();
40 | if (importReference == null) {
41 | continue;
42 | }
43 | final PsiElement qualifier = importReference.getQualifier();
44 | if (qualifier == null) {
45 | continue;
46 | }
47 | final String qualifierText = qualifier.getText();
48 | if (fqClassName.equals(qualifierText)) {
49 | return true;
50 | }
51 | }
52 | return false;
53 | }
54 |
55 | public static void importIfNot(final String fqClassName, final PsiElement context){
56 |
57 | final PsiFile file = context.getContainingFile();
58 | if (!(file instanceof PsiJavaFile)) {
59 | return;
60 | }
61 | final PsiJavaFile javaFile = (PsiJavaFile) file;
62 | final PsiImportList importList = javaFile.getImportList();
63 | if (importList == null) {
64 | return;
65 | }
66 |
67 | boolean imported = false;
68 | PsiImportStatement[] importStatements = importList.getImportStatements();
69 | for (PsiImportStatement importStatement : importStatements) {
70 | if (fqClassName.equals(importStatement.getQualifiedName())){
71 | imported = true;
72 | break;
73 | }
74 | }
75 |
76 | if (!imported){
77 | // UIUtil.invokeLaterIfNeeded(new Runnable() {
78 | // @Override
79 | // public void run() {
80 | // importList.add(JavaPsiFacade.getInstance(context.getProject()).getElementFactory().createTypeElement(PsiType.getTypeByName(fqClassName, context.getProject(), GlobalSearchScope.allScope(context.getProject()))));
81 | // }
82 | // });
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/util/LayoutUtil.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.util;
2 |
3 | import com.intellij.openapi.editor.Editor;
4 | import com.intellij.openapi.project.Project;
5 | import com.intellij.psi.*;
6 | import com.intellij.psi.search.FilenameIndex;
7 | import com.intellij.psi.search.GlobalSearchScope;
8 | import com.intellij.psi.util.PsiTreeUtil;
9 | import com.intellij.psi.util.PsiUtilBase;
10 | import com.intellij.psi.xml.XmlAttribute;
11 | import com.intellij.psi.xml.XmlTag;
12 | import org.sssta.androidtools.model.ViewModel;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by cauchywei on 15/8/17.
19 | */
20 | public class LayoutUtil {
21 |
22 | public static PsiFile findLayoutXmlFile(Project project, Editor editor){
23 |
24 | PsiElement layoutResourceElement = findLayoutResourceElement(project, editor);
25 |
26 | if (layoutResourceElement == null || !isValidLayoutResource(layoutResourceElement)) {
27 | return null;
28 | }
29 |
30 | return findXmlByLayoutResourceElement(layoutResourceElement);
31 | }
32 |
33 | public static PsiElement findLayoutResourceElement(Project project, Editor editor){
34 | return findLayoutResourceElementByMethodCallExpress(findLayoutSetMethod(project, editor));
35 | }
36 |
37 | public static PsiElement findLayoutResourceElementByMethodCallExpress(PsiMethodCallExpression methodCallExpression) {
38 |
39 | if (methodCallExpression == null) {
40 | return null;
41 | }
42 |
43 | PsiExpression[] expressions = methodCallExpression.getArgumentList().getExpressions();
44 | if (expressions.length == 0){
45 | return null;
46 | }
47 | return expressions[0];
48 | }
49 |
50 | public static PsiMethodCallExpression findLayoutSetMethod(Project project, Editor editor){
51 |
52 | if (project == null || editor == null) {
53 | return null;
54 | }
55 |
56 | PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
57 |
58 | if (psiFile == null) {
59 | return null;
60 | }
61 |
62 | PsiElement elementAtCaret = PsiUtilBase.getElementAtCaret(editor);
63 | return PsiTreeUtil.getParentOfType(elementAtCaret, PsiMethodCallExpression.class);
64 | }
65 |
66 | public static boolean isValidLayoutResource(PsiElement psiElement) {
67 |
68 | if (psiElement == null) {
69 | return false;
70 | }
71 |
72 | String name;
73 | if (psiElement instanceof PsiReferenceExpression){
74 | name = ((PsiReferenceExpression)psiElement).getReferenceName();
75 | }else {
76 | name = psiElement.getText();
77 | }
78 |
79 |
80 | return !"R.layout".startsWith(name);
81 |
82 | }
83 |
84 | public static PsiFile findXmlByLayoutResourceElement(PsiElement layoutResource){
85 |
86 | Project project = layoutResource.getProject();
87 | String xmlName = layoutResource.getLastChild().getText() + ".xml";
88 | PsiFile[] xmlFiles = FilenameIndex.getFilesByName(project, xmlName, GlobalSearchScope.allScope(project));
89 |
90 | if (xmlFiles == null || xmlFiles.length == 0){
91 | return null;
92 | }
93 |
94 | return xmlFiles[0];
95 |
96 | }
97 |
98 |
99 |
100 | public static List getContainingIdViewsInXml(PsiFile xmlFile){
101 |
102 | return getContainingAttributeViewsInXml(xmlFile,"android:id");
103 | }
104 |
105 | public static List getContainingAttributeViewsInXml(PsiFile xmlFile, final String attributeName) {
106 |
107 | final String name = xmlFile.getName();
108 | final List views = new ArrayList<>();
109 | xmlFile.accept(new XmlRecursiveElementVisitor() {
110 | @Override
111 | public void visitXmlTag(XmlTag tag) {
112 | super.visitXmlTag(tag);
113 | XmlAttribute idAttribute = tag.getAttribute(attributeName);
114 |
115 | if (idAttribute != null) {
116 | String id = idAttribute.getValue();
117 | if (id != null && id.matches("^@\\+?id/.*")){
118 | views.add(new ViewModel(name,id,tag.getName()));
119 | }
120 | }
121 | }
122 | });
123 |
124 | return views;
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/org/sssta/androidtools/util/Levenshtein.java:
--------------------------------------------------------------------------------
1 | package org.sssta.androidtools.util;
2 |
3 | /**
4 | * Created by cauchywei on 15/8/17.
5 | */
6 | public class Levenshtein {
7 |
8 |
9 | public static void levenshtein(String str1,String str2) {
10 | //计算两个字符串的长度。
11 | int len1 = str1.length();
12 | int len2 = str2.length();
13 | //建立上面说的数组,比字符长度大一个空间
14 | int[][] dif = new int[len1 + 1][len2 + 1];
15 | //赋初值,步骤B。
16 | for (int a = 0; a <= len1; a++) {
17 | dif[a][0] = a;
18 | }
19 | for (int a = 0; a <= len2; a++) {
20 | dif[0][a] = a;
21 | }
22 | //计算两个字符是否一样,计算左上的值
23 | int temp;
24 | for (int i = 1; i <= len1; i++) {
25 | for (int j = 1; j <= len2; j++) {
26 | if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
27 | temp = 0;
28 | } else {
29 | temp = 1;
30 | }
31 | //取三个值中最小的
32 | dif[i][j] = min(dif[i - 1][j - 1] + temp, dif[i][j - 1] + 1,
33 | dif[i - 1][j] + 1);
34 | }
35 | }
36 | System.out.println("字符串\""+str1+"\"与\""+str2+"\"的比较");
37 | //取数组右下角的值,同样不同位置代表不同字符串的比较
38 | System.out.println("差异步骤:"+dif[len1][len2]);
39 | //计算相似度
40 | float similarity =1 - (float) dif[len1][len2] / Math.max(str1.length(), str2.length());
41 | System.out.println("相似度:"+similarity);
42 | }
43 |
44 | private static int min(int... is) {
45 | int min = Integer.MAX_VALUE;
46 | for (int i : is) {
47 | if (min > i) {
48 | min = i;
49 | }
50 | }
51 | return min;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/postfixTemplates/ClickListenerPostfixTemplate/description.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | generate click listener according to context
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/postfixTemplates/ToastPostfixTemplate/description.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Show a Toast.
4 |
5 | Text after this comment will not be shown in tooltips.
6 |
7 |
--------------------------------------------------------------------------------