summaryrefslogtreecommitdiff
path: root/xml/impl
diff options
context:
space:
mode:
Diffstat (limited to 'xml/impl')
-rw-r--r--xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form21
-rw-r--r--xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java10
-rw-r--r--xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.form17
-rw-r--r--xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.java10
-rw-r--r--xml/impl/src/com/intellij/application/options/HtmlLanguageCodeStyleSettings.java11
-rw-r--r--xml/impl/src/com/intellij/application/options/XmlLanguageCodeStyleSettingsProvider.java9
-rw-r--r--xml/impl/src/com/intellij/codeInsight/completion/HtmlTextCompletionConfidence.java4
-rw-r--r--xml/impl/src/com/intellij/codeInsight/template/emmet/filters/TrimZenCodingFilter.java25
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java17
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/BrowserSelector.java4
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.form2
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.java216
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/ConfigurableWebBrowser.java11
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/DefaultBrowserPolicy.java20
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/WebBrowserManager.java105
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/actions/BaseOpenInBrowserAction.java2
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/actions/OpenFileInDefaultBrowserAction.java12
-rw-r--r--xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java11
-rw-r--r--xml/impl/src/com/intellij/psi/formatter/xml/AbstractXmlBlock.java28
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form40
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java47
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java116
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java78
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java231
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.java102
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerRootsProvider.java150
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/PathInfo.java47
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/PrefixlessWebServerRootsProvider.java18
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/WebServerFileHandler.java35
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandler.java57
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandlerAdapter.java37
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathToFileManager.java142
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/WebServerRootsProvider.java21
-rw-r--r--xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java108
-rw-r--r--xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java5
-rw-r--r--xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java149
-rw-r--r--xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java149
-rw-r--r--xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java21
-rw-r--r--xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java249
-rw-r--r--xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java86
-rw-r--r--xml/impl/xml.iml2
41 files changed, 2242 insertions, 183 deletions
diff --git a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form
index 01f7df00f6bd..0ddd4cf577f3 100644
--- a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form
+++ b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form
@@ -17,7 +17,7 @@
<border type="none"/>
<children/>
</xy>
- <grid id="1b801" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <grid id="1b801" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -27,14 +27,14 @@
<children>
<scrollpane id="3e3f1" class="com.intellij.ui.components.JBScrollPane" binding="myJBScrollPane" custom-create="true">
<constraints>
- <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<horizontalScrollBarPolicy value="31"/>
</properties>
<border type="empty"/>
<children>
- <grid id="b4cfb" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <grid id="b4cfb" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints/>
<properties/>
@@ -43,7 +43,7 @@
<grid id="ca5bb" layout-manager="GridLayoutManager" row-count="7" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
- <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -185,7 +185,7 @@
<grid id="b8acc" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
- <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -277,7 +277,7 @@
<grid id="6dece" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
- <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -336,6 +336,15 @@
</component>
</children>
</grid>
+ <grid id="eb695" binding="myRightMarginPanel" custom-create="true" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children/>
+ </grid>
</children>
</grid>
</children>
diff --git a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java
index ba5a72a80beb..476181ff256d 100644
--- a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java
+++ b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java
@@ -15,6 +15,7 @@
*/
package com.intellij.application.options;
+import com.intellij.application.options.codeStyle.RightMarginForm;
import com.intellij.ide.highlighter.XmlHighlighterFactory;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
@@ -30,6 +31,7 @@ import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformIcons;
+import com.intellij.util.ui.Centerizer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -64,6 +66,8 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
private JCheckBox myShouldKeepLineBreaksInText;
private TextFieldWithBrowseButton myDontBreakIfInlineContent;
private JBScrollPane myJBScrollPane;
+ private JPanel myRightMarginPanel;
+ private RightMarginForm myRightMarginForm;
public CodeStyleHtmlPanel(CodeStyleSettings settings) {
super(settings);
@@ -95,6 +99,8 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
}
private void createUIComponents() {
+ myRightMarginForm = new RightMarginForm(StdFileTypes.HTML.getLanguage(), getSettings());
+ myRightMarginPanel = myRightMarginForm.getTopPanel();
myJBScrollPane = new JBScrollPane() {
@Override
public Dimension getPreferredSize() {
@@ -158,6 +164,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
settings.HTML_KEEP_WHITESPACES_INSIDE = myKeepWhiteSpacesTagNames.getText();
settings.HTML_KEEP_LINE_BREAKS = myShouldKeepBlankLines.isSelected();
settings.HTML_KEEP_LINE_BREAKS_IN_TEXT = myShouldKeepLineBreaksInText.isSelected();
+ myRightMarginForm.apply(settings);
}
private static int getIntValue(JTextField keepBlankLines) {
@@ -190,6 +197,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
myInlineElementsTagNames.setText(settings.HTML_INLINE_ELEMENTS);
myDontBreakIfInlineContent.setText(settings.HTML_DONT_ADD_BREAKS_IF_INLINE_CONTENT);
myKeepWhiteSpacesTagNames.setText(settings.HTML_KEEP_WHITESPACES_INSIDE);
+ myRightMarginForm.reset(settings);
}
@Override
@@ -260,7 +268,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
return true;
}
- return false;
+ return myRightMarginForm.isModified(settings);
}
@Override
diff --git a/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.form b/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.form
index 28d48d089cc1..c7895930be0f 100644
--- a/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.form
+++ b/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.form
@@ -17,7 +17,7 @@
</properties>
<border type="empty"/>
<children>
- <grid id="265f" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <grid id="265f" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints/>
<properties/>
@@ -26,7 +26,7 @@
<grid id="417cd" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
- <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -114,7 +114,7 @@
<grid id="e7045" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
- <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -176,7 +176,7 @@
<grid id="bcc6c" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
- <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
@@ -215,6 +215,15 @@
</component>
</children>
</grid>
+ <grid id="3d661" binding="myRightMarginPanel" custom-create="true" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children/>
+ </grid>
</children>
</grid>
</children>
diff --git a/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.java b/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.java
index ab3126162dde..8767e00de1ec 100644
--- a/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.java
+++ b/xml/impl/src/com/intellij/application/options/CodeStyleXmlPanel.java
@@ -15,6 +15,7 @@
*/
package com.intellij.application.options;
+import com.intellij.application.options.codeStyle.RightMarginForm;
import com.intellij.ide.highlighter.XmlHighlighterFactory;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
@@ -25,6 +26,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.xml.XmlCodeStyleSettings;
import com.intellij.ui.components.JBScrollPane;
+import org.apache.xmlbeans.XmlLanguage;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -48,6 +50,8 @@ public class CodeStyleXmlPanel extends CodeStyleAbstractPanel{
private JComboBox myWhiteSpaceAroundCDATA;
private JCheckBox myKeepWhitespaceInsideCDATACheckBox;
private JBScrollPane myJBScrollPane;
+ private JPanel myRightMarginPanel;
+ private RightMarginForm myRightMarginForm;
public CodeStyleXmlPanel(CodeStyleSettings settings) {
super(settings);
@@ -83,6 +87,7 @@ public class CodeStyleXmlPanel extends CodeStyleAbstractPanel{
xmlSettings.XML_SPACE_INSIDE_EMPTY_TAG = myInEmptyTag.isSelected();
xmlSettings.XML_WHITE_SPACE_AROUND_CDATA = myWhiteSpaceAroundCDATA.getSelectedIndex();
xmlSettings.XML_KEEP_WHITE_SPACES_INSIDE_CDATA = myKeepWhitespaceInsideCDATACheckBox.isSelected();
+ myRightMarginForm.apply(settings);
}
private int getIntValue(JTextField keepBlankLines) {
@@ -109,6 +114,7 @@ public class CodeStyleXmlPanel extends CodeStyleAbstractPanel{
myWrapText.setSelected(wrapText(settings));
myWhiteSpaceAroundCDATA.setSelectedIndex(xmlSettings.XML_WHITE_SPACE_AROUND_CDATA);
myKeepWhitespaceInsideCDATACheckBox.setSelected(xmlSettings.XML_KEEP_WHITE_SPACES_INSIDE_CDATA);
+ myRightMarginForm.reset(settings);
}
@Override
@@ -156,7 +162,7 @@ public class CodeStyleXmlPanel extends CodeStyleAbstractPanel{
return true;
}
- return false;
+ return myRightMarginForm.isModified(settings);
}
private boolean wrapText(final CodeStyleSettings settings) {
@@ -193,5 +199,7 @@ public class CodeStyleXmlPanel extends CodeStyleAbstractPanel{
return new Dimension(prefSize.width + 15, prefSize.height);
}
};
+ myRightMarginForm = new RightMarginForm(StdFileTypes.XML.getLanguage(), getSettings());
+ myRightMarginPanel = myRightMarginForm.getTopPanel();
}
}
diff --git a/xml/impl/src/com/intellij/application/options/HtmlLanguageCodeStyleSettings.java b/xml/impl/src/com/intellij/application/options/HtmlLanguageCodeStyleSettings.java
index aa36d1b9aa60..d970ee5008cf 100644
--- a/xml/impl/src/com/intellij/application/options/HtmlLanguageCodeStyleSettings.java
+++ b/xml/impl/src/com/intellij/application/options/HtmlLanguageCodeStyleSettings.java
@@ -17,10 +17,13 @@ package com.intellij.application.options;
import com.intellij.lang.Language;
import com.intellij.lang.html.HTMLLanguage;
+import com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
import org.jetbrains.annotations.NotNull;
+import java.util.Set;
+
/**
* @author Rustam Vishnyakov
*/
@@ -37,6 +40,14 @@ public class HtmlLanguageCodeStyleSettings extends LanguageCodeStyleSettingsProv
}
@Override
+ public void customizeSettings(@NotNull CodeStyleSettingsCustomizable consumer,
+ @NotNull SettingsType settingsType) {
+ if (settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS) {
+ consumer.showStandardOptions("RIGHT_MARGIN");
+ }
+ }
+
+ @Override
public CommonCodeStyleSettings getDefaultCommonSettings() {
CommonCodeStyleSettings defaultSettings = new CommonCodeStyleSettings(HTMLLanguage.INSTANCE);
defaultSettings.initIndentOptions();
diff --git a/xml/impl/src/com/intellij/application/options/XmlLanguageCodeStyleSettingsProvider.java b/xml/impl/src/com/intellij/application/options/XmlLanguageCodeStyleSettingsProvider.java
index 651c71e7ee7d..89311331d59b 100644
--- a/xml/impl/src/com/intellij/application/options/XmlLanguageCodeStyleSettingsProvider.java
+++ b/xml/impl/src/com/intellij/application/options/XmlLanguageCodeStyleSettingsProvider.java
@@ -17,6 +17,7 @@ package com.intellij.application.options;
import com.intellij.lang.Language;
import com.intellij.lang.xml.XMLLanguage;
+import com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
import com.intellij.util.PlatformUtils;
@@ -41,6 +42,14 @@ public class XmlLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSetti
}
@Override
+ public void customizeSettings(@NotNull CodeStyleSettingsCustomizable consumer,
+ @NotNull SettingsType settingsType) {
+ if (settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS) {
+ consumer.showStandardOptions("RIGHT_MARGIN");
+ }
+ }
+
+ @Override
public CommonCodeStyleSettings getDefaultCommonSettings() {
CommonCodeStyleSettings xmlSettings = new CommonCodeStyleSettings(getLanguage());
CommonCodeStyleSettings.IndentOptions indentOptions = xmlSettings.initIndentOptions();
diff --git a/xml/impl/src/com/intellij/codeInsight/completion/HtmlTextCompletionConfidence.java b/xml/impl/src/com/intellij/codeInsight/completion/HtmlTextCompletionConfidence.java
index ec6c806ffa1f..6d95604dc077 100644
--- a/xml/impl/src/com/intellij/codeInsight/completion/HtmlTextCompletionConfidence.java
+++ b/xml/impl/src/com/intellij/codeInsight/completion/HtmlTextCompletionConfidence.java
@@ -33,7 +33,9 @@ public class HtmlTextCompletionConfidence extends CompletionConfidence {
if (node != null && node.getElementType() == XmlTokenType.XML_DATA_CHARACTERS) {
PsiElement parent = contextElement.getParent();
if (parent instanceof XmlText || parent instanceof XmlDocument) {
- String prefix = contextElement.getText().substring(0, offset - contextElement.getTextRange().getStartOffset());
+ String contextElementText = contextElement.getText();
+ int endOffset = offset - contextElement.getTextRange().getStartOffset();
+ String prefix = contextElementText.substring(0, Math.min(contextElementText.length(), endOffset));
if (!StringUtil.startsWithChar(prefix, '<') && !StringUtil.startsWithChar(prefix, '&')) {
return ThreeState.YES;
}
diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/TrimZenCodingFilter.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/TrimZenCodingFilter.java
index ceb0d1dd61b9..421fbdd46564 100644
--- a/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/TrimZenCodingFilter.java
+++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/TrimZenCodingFilter.java
@@ -18,6 +18,7 @@ package com.intellij.codeInsight.template.emmet.filters;
import com.intellij.codeInsight.template.emmet.nodes.GenerationNode;
import com.intellij.codeInsight.template.emmet.tokens.TemplateToken;
import com.intellij.lang.xml.XMLLanguage;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.XmlElementVisitor;
import com.intellij.psi.xml.XmlDocument;
@@ -25,6 +26,7 @@ import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlTagValue;
import org.jetbrains.annotations.NotNull;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@@ -57,18 +59,27 @@ public class TrimZenCodingFilter extends ZenCodingFilter {
if (document != null) {
XmlTag tag = document.getRootTag();
if (tag != null && !tag.getText().isEmpty()) {
- new XmlElementVisitor() {
+ tag.accept(new XmlElementVisitor() {
@Override
- public void visitXmlTag(XmlTag tag) {
- if(!tag.isEmpty()) {
- XmlTagValue tagValue = tag.getValue();
- tagValue.setText(PATTERN.matcher(tagValue.getText()).replaceAll(""));
+ public void visitXmlTag(final XmlTag tag) {
+ if (!tag.isEmpty()) {
+ final XmlTagValue tagValue = tag.getValue();
+ final Matcher matcher = PATTERN.matcher(tagValue.getText());
+ if (matcher.matches()) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ tagValue.setText(matcher.replaceAll(""));
+ }
+ });
+ }
}
tag.acceptChildren(this);
}
- }.visitXmlTag(tag);
+ });
return tag.getText();
- } else {
+ }
+ else {
return PATTERN.matcher(document.getText()).replaceAll("");
}
}
diff --git a/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java b/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java
index 86d2994c0e00..4f7f4efa5085 100644
--- a/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java
+++ b/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java
@@ -18,6 +18,7 @@ package com.intellij.ide.browsers;
import com.intellij.concurrency.JobScheduler;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.util.ExecUtil;
+import com.intellij.ide.GeneralSettings;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.ShowSettingsUtil;
@@ -25,14 +26,30 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.AppUIUtil;
+import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
final class BrowserLauncherImpl extends BrowserLauncherAppless {
@Override
+ protected void browseUsingNotSystemDefaultBrowserPolicy(@NotNull URI uri, @NotNull GeneralSettings settings, @Nullable Project project) {
+ WebBrowserManager browserManager = WebBrowserManager.getInstance();
+ if (browserManager.getDefaultBrowserPolicy() == DefaultBrowserPolicy.FIRST) {
+ WebBrowser browser = browserManager.getFirstActiveBrowser();
+ if (browser != null) {
+ browseUsingPath(uri.toString(), null, browser, project, ArrayUtil.EMPTY_STRING_ARRAY);
+ return;
+ }
+ }
+
+ super.browseUsingNotSystemDefaultBrowserPolicy(uri, settings, project);
+ }
+
+ @Override
protected void doShowError(@Nullable final String error, @Nullable final WebBrowser browser, @Nullable final Project project, final String title, @Nullable final Runnable launchTask) {
AppUIUtil.invokeOnEdt(new Runnable() {
@Override
diff --git a/xml/impl/src/com/intellij/ide/browsers/BrowserSelector.java b/xml/impl/src/com/intellij/ide/browsers/BrowserSelector.java
index 3ae67def8424..af04e5896387 100644
--- a/xml/impl/src/com/intellij/ide/browsers/BrowserSelector.java
+++ b/xml/impl/src/com/intellij/ide/browsers/BrowserSelector.java
@@ -81,8 +81,8 @@ public class BrowserSelector {
boolean hasFocus) {
Icon baseIcon;
if (value == null) {
- WebBrowser defaultBrowser = WebBrowserManager.getInstance().getDefaultBrowser();
- baseIcon = defaultBrowser == null ? PlatformIcons.WEB_ICON : defaultBrowser.getIcon();
+ WebBrowser firstBrowser = WebBrowserManager.getInstance().getFirstActiveBrowser();
+ baseIcon = firstBrowser == null ? PlatformIcons.WEB_ICON : firstBrowser.getIcon();
}
else {
baseIcon = value.getIcon();
diff --git a/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.form b/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.form
index 52d074347bbc..23d7bf2ea873 100644
--- a/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.form
+++ b/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.form
@@ -22,7 +22,7 @@
</constraints>
<properties/>
</component>
- <component id="95daa" class="com.intellij.openapi.ui.ComboBox" binding="defaultBrowserComboBox">
+ <component id="95daa" class="com.intellij.openapi.ui.ComboBox" binding="defaultBrowserPolicyComboBox">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
diff --git a/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.java b/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.java
index 477d527ecbac..f43c0f6f4929 100644
--- a/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.java
+++ b/xml/impl/src/com/intellij/ide/browsers/BrowserSettingsPanel.java
@@ -23,7 +23,6 @@ import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Comparing;
-import com.intellij.ui.EnumComboBoxModel;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.ui.TitledSeparator;
import com.intellij.util.Function;
@@ -34,6 +33,7 @@ import com.intellij.util.ui.LocalPathCellEditor;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.table.IconTableCellRenderer;
import com.intellij.util.ui.table.TableModelEditor;
+import org.jdesktop.swingx.combobox.ListComboBoxModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -46,9 +46,9 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
+import java.util.ArrayList;
import java.util.UUID;
-import static com.intellij.ide.browsers.WebBrowserManager.DefaultBrowser;
import static com.intellij.util.ui.table.TableModelEditor.EditableColumnInfo;
final class BrowserSettingsPanel {
@@ -73,7 +73,7 @@ final class BrowserSettingsPanel {
}
};
- private static final ColumnInfo[] COLUMNS = {new EditableColumnInfo<ConfigurableWebBrowser, Boolean>() {
+ private static final EditableColumnInfo<ConfigurableWebBrowser, Boolean> ACTIVE_COLUMN_INFO = new EditableColumnInfo<ConfigurableWebBrowser, Boolean>() {
@Override
public Class getColumnClass() {
return Boolean.class;
@@ -88,44 +88,49 @@ final class BrowserSettingsPanel {
public void setValue(ConfigurableWebBrowser item, Boolean value) {
item.setActive(value);
}
- }, new EditableColumnInfo<ConfigurableWebBrowser, String>("Name") {
- @Override
- public String valueOf(ConfigurableWebBrowser item) {
- return item.getName();
- }
+ };
- @Override
- public void setValue(ConfigurableWebBrowser item, String value) {
- item.setName(value);
- }
- }, new ColumnInfo<ConfigurableWebBrowser, BrowserFamily>("Family") {
- @Override
- public Class getColumnClass() {
- return BrowserFamily.class;
- }
+ private static final ColumnInfo[] COLUMNS = {ACTIVE_COLUMN_INFO,
+ new EditableColumnInfo<ConfigurableWebBrowser, String>("Name") {
+ @Override
+ public String valueOf(ConfigurableWebBrowser item) {
+ return item.getName();
+ }
- @Override
- public BrowserFamily valueOf(ConfigurableWebBrowser item) {
- return item.getFamily();
- }
+ @Override
+ public void setValue(ConfigurableWebBrowser item, String value) {
+ item.setName(value);
+ }
+ },
+ new ColumnInfo<ConfigurableWebBrowser, BrowserFamily>("Family") {
+ @Override
+ public Class getColumnClass() {
+ return BrowserFamily.class;
+ }
- @Override
- public void setValue(ConfigurableWebBrowser item, BrowserFamily value) {
- item.setFamily(value);
- item.setSpecificSettings(value.createBrowserSpecificSettings());
- }
+ @Override
+ public BrowserFamily valueOf(ConfigurableWebBrowser item) {
+ return item.getFamily();
+ }
- @Nullable
- @Override
- public TableCellRenderer getRenderer(ConfigurableWebBrowser item) {
- return IconTableCellRenderer.ICONABLE;
- }
+ @Override
+ public void setValue(ConfigurableWebBrowser item, BrowserFamily value) {
+ item.setFamily(value);
+ item.setSpecificSettings(value.createBrowserSpecificSettings());
+ }
- @Override
- public boolean isCellEditable(ConfigurableWebBrowser item) {
- return !WebBrowserManager.getInstance().isPredefinedBrowser(item);
- }
- }, PATH_COLUMN_INFO};
+ @Nullable
+ @Override
+ public TableCellRenderer getRenderer(ConfigurableWebBrowser item) {
+ return IconTableCellRenderer.ICONABLE;
+ }
+
+ @Override
+ public boolean isCellEditable(ConfigurableWebBrowser item) {
+ return !WebBrowserManager.getInstance().isPredefinedBrowser(item);
+ }
+ },
+ PATH_COLUMN_INFO};
private JPanel root;
@@ -138,7 +143,7 @@ final class BrowserSettingsPanel {
@SuppressWarnings("UnusedDeclaration")
private JComponent browsersTable;
- private ComboBox defaultBrowserComboBox;
+ private ComboBox defaultBrowserPolicyComboBox;
private TableModelEditor<ConfigurableWebBrowser> browsersEditor;
@@ -148,53 +153,55 @@ final class BrowserSettingsPanel {
alternativeBrowserPathField.addBrowseFolderListener(IdeBundle.message("title.select.path.to.browser"), null, null, APP_FILE_CHOOSER_DESCRIPTOR);
defaultBrowserPanel.setBorder(TitledSeparator.EMPTY_BORDER);
- //noinspection unchecked
- defaultBrowserComboBox.setModel(new EnumComboBoxModel<DefaultBrowser>(DefaultBrowser.class));
- if (BrowserLauncherAppless.canStartDefaultBrowser()) {
- defaultBrowserComboBox.addItemListener(new ItemListener() {
- @Override
- public void itemStateChanged(ItemEvent e) {
- boolean customPathEnabled = e.getItem() == DefaultBrowser.ALTERNATIVE;
- if (e.getStateChange() == ItemEvent.DESELECTED) {
- if (customPathEnabled) {
- customPathValue = alternativeBrowserPathField.getText();
- }
- }
- else if (e.getStateChange() == ItemEvent.SELECTED) {
- alternativeBrowserPathField.setEnabled(customPathEnabled);
- updateCustomPathTextFieldValue((DefaultBrowser)e.getItem());
- }
- }
- });
+ ArrayList<DefaultBrowserPolicy> defaultBrowserPolicies = new ArrayList<DefaultBrowserPolicy>();
+ if (BrowserLauncherAppless.canUseSystemDefaultBrowserPolicy()) {
+ defaultBrowserPolicies.add(DefaultBrowserPolicy.SYSTEM);
+ }
+ defaultBrowserPolicies.add(DefaultBrowserPolicy.FIRST);
+ defaultBrowserPolicies.add(DefaultBrowserPolicy.ALTERNATIVE);
- defaultBrowserComboBox.setRenderer(new ListCellRendererWrapper<DefaultBrowser>() {
- @Override
- public void customize(JList list, DefaultBrowser value, int index, boolean selected, boolean hasFocus) {
- String name;
- switch (value) {
- case SYSTEM:
- name = "System default";
- break;
- case FIRST:
- name = "First listed";
- break;
- case ALTERNATIVE:
- name = "Custom path";
- break;
- default:
- throw new IllegalStateException();
+ //noinspection Since15,unchecked
+ defaultBrowserPolicyComboBox.setModel(new ListComboBoxModel<DefaultBrowserPolicy>(defaultBrowserPolicies));
+ defaultBrowserPolicyComboBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ boolean customPathEnabled = e.getItem() == DefaultBrowserPolicy.ALTERNATIVE;
+ if (e.getStateChange() == ItemEvent.DESELECTED) {
+ if (customPathEnabled) {
+ customPathValue = alternativeBrowserPathField.getText();
}
+ }
+ else if (e.getStateChange() == ItemEvent.SELECTED) {
+ alternativeBrowserPathField.setEnabled(customPathEnabled);
+ updateCustomPathTextFieldValue((DefaultBrowserPolicy)e.getItem());
+ }
+ }
+ });
- setText(name);
+ defaultBrowserPolicyComboBox.setRenderer(new ListCellRendererWrapper<DefaultBrowserPolicy>() {
+ @Override
+ public void customize(JList list, DefaultBrowserPolicy value, int index, boolean selected, boolean hasFocus) {
+ String name;
+ switch (value) {
+ case SYSTEM:
+ name = "System default";
+ break;
+ case FIRST:
+ name = "First listed";
+ break;
+ case ALTERNATIVE:
+ name = "Custom path";
+ break;
+ default:
+ throw new IllegalStateException();
}
- });
- if (UIUtil.isUnderAquaLookAndFeel()) {
- defaultBrowserComboBox.setBorder(new EmptyBorder(3, 0, 0, 0));
+ setText(name);
}
- }
- else {
- defaultBrowserComboBox.setVisible(false);
+ });
+
+ if (UIUtil.isUnderAquaLookAndFeel()) {
+ defaultBrowserPolicyComboBox.setBorder(new EmptyBorder(3, 0, 0, 0));
}
clearExtractedFiles.addActionListener(new ActionListener() {
@@ -205,11 +212,11 @@ final class BrowserSettingsPanel {
});
}
- private void updateCustomPathTextFieldValue(DefaultBrowser browser) {
- if (browser == DefaultBrowser.ALTERNATIVE) {
+ private void updateCustomPathTextFieldValue(@NotNull DefaultBrowserPolicy browser) {
+ if (browser == DefaultBrowserPolicy.ALTERNATIVE) {
alternativeBrowserPathField.setText(customPathValue);
}
- else if (browser == DefaultBrowser.FIRST) {
+ else if (browser == DefaultBrowserPolicy.FIRST) {
setCustomPathToFirstListed();
}
else {
@@ -272,18 +279,18 @@ final class BrowserSettingsPanel {
.modelListener(new TableModelEditor.DataChangedListener<ConfigurableWebBrowser>() {
@Override
public void tableChanged(TableModelEvent event) {
- update(event.getFirstRow());
+ update();
}
@Override
public void dataChanged(@NotNull ColumnInfo<ConfigurableWebBrowser, ?> columnInfo, int rowIndex) {
- if (columnInfo == PATH_COLUMN_INFO) {
- update(rowIndex);
+ if (columnInfo == PATH_COLUMN_INFO || columnInfo == ACTIVE_COLUMN_INFO) {
+ update();
}
}
- private void update(int rowIndex) {
- if (rowIndex == 0 && getDefaultBrowser() == DefaultBrowser.FIRST) {
+ private void update() {
+ if (getDefaultBrowser() == DefaultBrowserPolicy.FIRST) {
setCustomPathToFirstListed();
}
}
@@ -293,7 +300,15 @@ final class BrowserSettingsPanel {
private void setCustomPathToFirstListed() {
ListTableModel<ConfigurableWebBrowser> model = browsersEditor.getModel();
- alternativeBrowserPathField.setText(model.getRowCount() == 0 ? "" : model.getRowValue(0).getPath());
+ for (int i = 0, n = model.getRowCount(); i < n; i++) {
+ ConfigurableWebBrowser browser = model.getRowValue(i);
+ if (browser.isActive() && browser.getPath() != null) {
+ alternativeBrowserPathField.setText(browser.getPath());
+ return;
+ }
+ }
+
+ alternativeBrowserPathField.setText("");
}
@NotNull
@@ -305,12 +320,12 @@ final class BrowserSettingsPanel {
WebBrowserManager browserManager = WebBrowserManager.getInstance();
GeneralSettings generalSettings = GeneralSettings.getInstance();
- DefaultBrowser defaultBrowser = getDefaultBrowser();
- if (browserManager.getDefaultBrowserMode() != defaultBrowser || generalSettings.isConfirmExtractFiles() != confirmExtractFiles.isSelected()) {
+ DefaultBrowserPolicy defaultBrowserPolicy = getDefaultBrowser();
+ if (browserManager.getDefaultBrowserPolicy() != defaultBrowserPolicy || generalSettings.isConfirmExtractFiles() != confirmExtractFiles.isSelected()) {
return true;
}
- if (defaultBrowser == DefaultBrowser.ALTERNATIVE &&
+ if (defaultBrowserPolicy == DefaultBrowserPolicy.ALTERNATIVE &&
!Comparing.strEqual(generalSettings.getBrowserPath(), alternativeBrowserPathField.getText())) {
return true;
}
@@ -321,7 +336,7 @@ final class BrowserSettingsPanel {
public void apply() {
GeneralSettings settings = GeneralSettings.getInstance();
- settings.setUseDefaultBrowser(getDefaultBrowser() == DefaultBrowser.SYSTEM);
+ settings.setUseDefaultBrowser(getDefaultBrowser() == DefaultBrowserPolicy.SYSTEM);
if (alternativeBrowserPathField.isEnabled()) {
settings.setBrowserPath(alternativeBrowserPathField.getText());
@@ -330,26 +345,27 @@ final class BrowserSettingsPanel {
settings.setConfirmExtractFiles(confirmExtractFiles.isSelected());
WebBrowserManager browserManager = WebBrowserManager.getInstance();
- browserManager.defaultBrowser = getDefaultBrowser();
+ browserManager.defaultBrowserPolicy = getDefaultBrowser();
browserManager.setList(browsersEditor.apply());
}
- private DefaultBrowser getDefaultBrowser() {
- return (DefaultBrowser)defaultBrowserComboBox.getSelectedItem();
+ private DefaultBrowserPolicy getDefaultBrowser() {
+ return (DefaultBrowserPolicy)defaultBrowserPolicyComboBox.getSelectedItem();
}
public void reset() {
- GeneralSettings settings = GeneralSettings.getInstance();
-
- DefaultBrowser defaultBrowser = WebBrowserManager.getInstance().getDefaultBrowserMode();
- defaultBrowserComboBox.setSelectedItem(defaultBrowser);
+ DefaultBrowserPolicy defaultBrowserPolicy = WebBrowserManager.getInstance().getDefaultBrowserPolicy();
+ DefaultBrowserPolicy effectiveDefaultBrowserPolicy = defaultBrowserPolicy == DefaultBrowserPolicy.SYSTEM && !BrowserLauncherAppless.canUseSystemDefaultBrowserPolicy()
+ ? DefaultBrowserPolicy.ALTERNATIVE : defaultBrowserPolicy;
+ defaultBrowserPolicyComboBox.setSelectedItem(effectiveDefaultBrowserPolicy);
+ GeneralSettings settings = GeneralSettings.getInstance();
confirmExtractFiles.setSelected(settings.isConfirmExtractFiles());
browsersEditor.reset(WebBrowserManager.getInstance().getList());
customPathValue = settings.getBrowserPath();
- alternativeBrowserPathField.setEnabled(defaultBrowser == DefaultBrowser.ALTERNATIVE);
- updateCustomPathTextFieldValue(defaultBrowser);
+ alternativeBrowserPathField.setEnabled(effectiveDefaultBrowserPolicy == DefaultBrowserPolicy.ALTERNATIVE);
+ updateCustomPathTextFieldValue(effectiveDefaultBrowserPolicy);
}
public void selectBrowser(@NotNull WebBrowser browser) {
diff --git a/xml/impl/src/com/intellij/ide/browsers/ConfigurableWebBrowser.java b/xml/impl/src/com/intellij/ide/browsers/ConfigurableWebBrowser.java
index 11502b5119a9..9f48b35905ae 100644
--- a/xml/impl/src/com/intellij/ide/browsers/ConfigurableWebBrowser.java
+++ b/xml/impl/src/com/intellij/ide/browsers/ConfigurableWebBrowser.java
@@ -56,7 +56,7 @@ final class ConfigurableWebBrowser extends WebBrowser {
@Override
public Icon getIcon() {
if (family == BrowserFamily.CHROME) {
- if (checkNameAndPath("Yandex")) {
+ if (WebBrowserManager.isYandexBrowser(this)) {
return AllIcons.Xml.Browsers.Yandex16;
}
else if (checkNameAndPath("Dartium") || checkNameAndPath("Chromium")) {
@@ -76,14 +76,7 @@ final class ConfigurableWebBrowser extends WebBrowser {
}
private boolean checkNameAndPath(@NotNull String what) {
- if (StringUtil.containsIgnoreCase(name, what)) {
- return true;
- }
- if (path != null) {
- int index = path.lastIndexOf('/');
- return index > 0 ? path.indexOf(what, index + 1) != -1 : path.contains(what);
- }
- return false;
+ return WebBrowserManager.checkNameAndPath(what, this);
}
@Nullable
diff --git a/xml/impl/src/com/intellij/ide/browsers/DefaultBrowserPolicy.java b/xml/impl/src/com/intellij/ide/browsers/DefaultBrowserPolicy.java
new file mode 100644
index 000000000000..4e1f0b2e0dc2
--- /dev/null
+++ b/xml/impl/src/com/intellij/ide/browsers/DefaultBrowserPolicy.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.ide.browsers;
+
+public enum DefaultBrowserPolicy {
+ SYSTEM, FIRST, ALTERNATIVE
+} \ No newline at end of file
diff --git a/xml/impl/src/com/intellij/ide/browsers/WebBrowserManager.java b/xml/impl/src/com/intellij/ide/browsers/WebBrowserManager.java
index ce51ee07eebc..0b2ba6cb18c1 100644
--- a/xml/impl/src/com/intellij/ide/browsers/WebBrowserManager.java
+++ b/xml/impl/src/com/intellij/ide/browsers/WebBrowserManager.java
@@ -17,10 +17,7 @@ package com.intellij.ide.browsers;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.Condition;
-import com.intellij.openapi.util.Conditions;
-import com.intellij.openapi.util.JDOMUtil;
-import com.intellij.openapi.util.SimpleModificationTracker;
+import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.SmartList;
import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters;
@@ -36,53 +33,75 @@ public class WebBrowserManager extends SimpleModificationTracker implements Pers
private static final Logger LOG = Logger.getInstance(WebBrowserManager.class);
// default standard browser ID must be constant across all IDE versions on all machines for all users
- private static final UUID DEFAULT_CHROME_ID = UUID.fromString("98CA6316-2F89-46D9-A9E5-FA9E2B0625B3");
+ private static final UUID PREDEFINED_CHROME_ID = UUID.fromString("98CA6316-2F89-46D9-A9E5-FA9E2B0625B3");
// public, but only internal use
- public static final UUID DEFAULT_FIREFOX_ID = UUID.fromString("A7BB68E0-33C0-4D6F-A81A-AAC1FDB870C8");
- private static final UUID DEFAULT_SAFARI_ID = UUID.fromString("E5120D43-2C3F-47EF-9F26-65E539E05186");
- private static final UUID DEFAULT_OPERA_ID = UUID.fromString("53E2F627-B1A7-4DFA-BFA7-5B83CC034776");
- private static final UUID DEFAULT_EXPLORER_ID = UUID.fromString("16BF23D4-93E0-4FFC-BFD6-CB13575177B0");
+ public static final UUID PREDEFINED_FIREFOX_ID = UUID.fromString("A7BB68E0-33C0-4D6F-A81A-AAC1FDB870C8");
+ private static final UUID PREDEFINED_SAFARI_ID = UUID.fromString("E5120D43-2C3F-47EF-9F26-65E539E05186");
+ private static final UUID PREDEFINED_OPERA_ID = UUID.fromString("53E2F627-B1A7-4DFA-BFA7-5B83CC034776");
+ private static final UUID PREDEFINED_YANDEX_ID = UUID.fromString("B1B2EC2C-20BD-4EE2-89C4-616DB004BCD4");
+ private static final UUID PREDEFINED_EXPLORER_ID = UUID.fromString("16BF23D4-93E0-4FFC-BFD6-CB13575177B0");
+
+ private static final List<ConfigurableWebBrowser> PREDEFINED_BROWSERS = Arrays.asList(
+ new ConfigurableWebBrowser(PREDEFINED_CHROME_ID, BrowserFamily.CHROME),
+ new ConfigurableWebBrowser(PREDEFINED_FIREFOX_ID, BrowserFamily.FIREFOX),
+ new ConfigurableWebBrowser(PREDEFINED_SAFARI_ID, BrowserFamily.SAFARI),
+ new ConfigurableWebBrowser(PREDEFINED_OPERA_ID, BrowserFamily.OPERA),
+ new ConfigurableWebBrowser(PREDEFINED_YANDEX_ID, BrowserFamily.CHROME, "Yandex", SystemInfo.isWindows ? "browser" : (SystemInfo.isMac ? "Yandex" : "yandex"), false, BrowserFamily.CHROME.createBrowserSpecificSettings()),
+ new ConfigurableWebBrowser(PREDEFINED_EXPLORER_ID, BrowserFamily.EXPLORER)
+ );
private List<ConfigurableWebBrowser> browsers;
- DefaultBrowser defaultBrowser = DefaultBrowser.SYSTEM;
+ DefaultBrowserPolicy defaultBrowserPolicy = DefaultBrowserPolicy.SYSTEM;
public WebBrowserManager() {
- browsers = new ArrayList<ConfigurableWebBrowser>();
- browsers.add(new ConfigurableWebBrowser(DEFAULT_CHROME_ID, BrowserFamily.CHROME));
- browsers.add(new ConfigurableWebBrowser(DEFAULT_FIREFOX_ID, BrowserFamily.FIREFOX));
- browsers.add(new ConfigurableWebBrowser(DEFAULT_SAFARI_ID, BrowserFamily.SAFARI));
- browsers.add(new ConfigurableWebBrowser(DEFAULT_OPERA_ID, BrowserFamily.OPERA));
- browsers.add(new ConfigurableWebBrowser(DEFAULT_EXPLORER_ID, BrowserFamily.EXPLORER));
+ browsers = new ArrayList<ConfigurableWebBrowser>(PREDEFINED_BROWSERS);
}
public static WebBrowserManager getInstance() {
return ServiceManager.getService(WebBrowserManager.class);
}
- boolean isPredefinedBrowser(@NotNull ConfigurableWebBrowser browser) {
- UUID id = browser.getId();
- return id.equals(DEFAULT_CHROME_ID) ||
- id.equals(DEFAULT_FIREFOX_ID) ||
- id.equals(DEFAULT_SAFARI_ID) ||
- id.equals(DEFAULT_OPERA_ID) ||
- id.equals(DEFAULT_EXPLORER_ID);
+ public static boolean isYandexBrowser(@NotNull WebBrowser browser) {
+ return browser.getFamily().equals(BrowserFamily.CHROME) && (browser.getId().equals(PREDEFINED_YANDEX_ID) || checkNameAndPath("Yandex", browser));
+ }
+
+ public static boolean isDartium(@NotNull WebBrowser browser) {
+ return browser.getFamily().equals(BrowserFamily.CHROME) && checkNameAndPath("Dartium", browser);
+ }
+
+ static boolean checkNameAndPath(@NotNull String what, @NotNull WebBrowser browser) {
+ if (StringUtil.containsIgnoreCase(browser.getName(), what)) {
+ return true;
+ }
+ String path = browser.getPath();
+ if (path != null) {
+ int index = path.lastIndexOf('/');
+ return index > 0 ? path.indexOf(what, index + 1) != -1 : path.contains(what);
+ }
+ return false;
}
- public enum DefaultBrowser {
- SYSTEM, FIRST, ALTERNATIVE
+ boolean isPredefinedBrowser(@NotNull ConfigurableWebBrowser browser) {
+ UUID id = browser.getId();
+ for (ConfigurableWebBrowser predefinedBrowser : PREDEFINED_BROWSERS) {
+ if (id.equals(predefinedBrowser.getId())) {
+ return true;
+ }
+ }
+ return false;
}
@NotNull
- public DefaultBrowser getDefaultBrowserMode() {
- return defaultBrowser;
+ public DefaultBrowserPolicy getDefaultBrowserPolicy() {
+ return defaultBrowserPolicy;
}
@Override
public Element getState() {
Element state = new Element("state");
- if (defaultBrowser != DefaultBrowser.SYSTEM) {
- state.setAttribute("default", defaultBrowser.name().toLowerCase(Locale.ENGLISH));
+ if (defaultBrowserPolicy != DefaultBrowserPolicy.SYSTEM) {
+ state.setAttribute("default", defaultBrowserPolicy.name().toLowerCase(Locale.ENGLISH));
}
for (ConfigurableWebBrowser browser : browsers) {
@@ -137,19 +156,19 @@ public class WebBrowserManager extends SimpleModificationTracker implements Pers
UUID id;
switch (family) {
case CHROME:
- id = DEFAULT_CHROME_ID;
+ id = PREDEFINED_CHROME_ID;
break;
case EXPLORER:
- id = DEFAULT_EXPLORER_ID;
+ id = PREDEFINED_EXPLORER_ID;
break;
case FIREFOX:
- id = DEFAULT_FIREFOX_ID;
+ id = PREDEFINED_FIREFOX_ID;
break;
case OPERA:
- id = DEFAULT_OPERA_ID;
+ id = PREDEFINED_OPERA_ID;
break;
case SAFARI:
- id = DEFAULT_SAFARI_ID;
+ id = PREDEFINED_SAFARI_ID;
break;
default:
@@ -180,7 +199,7 @@ public class WebBrowserManager extends SimpleModificationTracker implements Pers
String defaultValue = element.getAttributeValue("default");
if (!StringUtil.isEmpty(defaultValue)) {
try {
- defaultBrowser = DefaultBrowser.valueOf(defaultValue.toUpperCase(Locale.ENGLISH));
+ defaultBrowserPolicy = DefaultBrowserPolicy.valueOf(defaultValue.toUpperCase(Locale.ENGLISH));
}
catch (IllegalArgumentException e) {
LOG.warn(e);
@@ -225,6 +244,18 @@ public class WebBrowserManager extends SimpleModificationTracker implements Pers
specificSettings));
}
+ // add removed/new predefined browsers
+ int n = list.size();
+ pb: for (ConfigurableWebBrowser predefinedBrowser : PREDEFINED_BROWSERS) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < n; i++) {
+ if (list.get(i).getId().equals(predefinedBrowser.getId())) {
+ continue pb;
+ }
+ }
+ list.add(predefinedBrowser);
+ }
+
setList(list);
}
@@ -349,9 +380,9 @@ public class WebBrowserManager extends SimpleModificationTracker implements Pers
}
@Nullable
- public WebBrowser getDefaultBrowser() {
+ public WebBrowser getFirstActiveBrowser() {
for (ConfigurableWebBrowser browser : browsers) {
- if (browser.isActive()) {
+ if (browser.isActive() && browser.getPath() != null) {
return browser;
}
}
diff --git a/xml/impl/src/com/intellij/ide/browsers/actions/BaseOpenInBrowserAction.java b/xml/impl/src/com/intellij/ide/browsers/actions/BaseOpenInBrowserAction.java
index 8ec2a2e55b67..e3bf3b5e6f6f 100644
--- a/xml/impl/src/com/intellij/ide/browsers/actions/BaseOpenInBrowserAction.java
+++ b/xml/impl/src/com/intellij/ide/browsers/actions/BaseOpenInBrowserAction.java
@@ -151,7 +151,7 @@ public abstract class BaseOpenInBrowserAction extends DumbAwareAction {
boolean applicable = false;
WebBrowserUrlProvider provider = null;
if (request != null) {
- applicable = HtmlUtil.isHtmlFile(request.getFile()) && !(request.getVirtualFile() instanceof LightVirtualFile);
+ applicable = WebBrowserServiceImpl.isHtmlOrXmlFile(request.getFile()) && !(request.getVirtualFile() instanceof LightVirtualFile);
if (!applicable) {
provider = WebBrowserServiceImpl.getProvider(request);
applicable = provider != null;
diff --git a/xml/impl/src/com/intellij/ide/browsers/actions/OpenFileInDefaultBrowserAction.java b/xml/impl/src/com/intellij/ide/browsers/actions/OpenFileInDefaultBrowserAction.java
index 45cf021bbef8..1ead1b6faeb4 100644
--- a/xml/impl/src/com/intellij/ide/browsers/actions/OpenFileInDefaultBrowserAction.java
+++ b/xml/impl/src/com/intellij/ide/browsers/actions/OpenFileInDefaultBrowserAction.java
@@ -16,10 +16,7 @@
package com.intellij.ide.browsers.actions;
import com.intellij.ide.GeneralSettings;
-import com.intellij.ide.browsers.OpenInBrowserRequest;
-import com.intellij.ide.browsers.WebBrowser;
-import com.intellij.ide.browsers.WebBrowserManager;
-import com.intellij.ide.browsers.WebBrowserUrlProvider;
+import com.intellij.ide.browsers.*;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
@@ -68,10 +65,11 @@ public class OpenFileInDefaultBrowserAction extends DumbAwareAction {
@Nullable
private static WebBrowser findUsingBrowser() {
WebBrowserManager browserManager = WebBrowserManager.getInstance();
- if (browserManager.getDefaultBrowserMode() == WebBrowserManager.DefaultBrowser.FIRST) {
- return browserManager.getDefaultBrowser();
+ DefaultBrowserPolicy defaultBrowserPolicy = browserManager.getDefaultBrowserPolicy();
+ if (defaultBrowserPolicy == DefaultBrowserPolicy.FIRST || (defaultBrowserPolicy == DefaultBrowserPolicy.SYSTEM && !BrowserLauncherAppless.canUseSystemDefaultBrowserPolicy())) {
+ return browserManager.getFirstActiveBrowser();
}
- else if (browserManager.getDefaultBrowserMode() == WebBrowserManager.DefaultBrowser.ALTERNATIVE) {
+ else if (defaultBrowserPolicy == DefaultBrowserPolicy.ALTERNATIVE) {
String path = GeneralSettings.getInstance().getBrowserPath();
if (!StringUtil.isEmpty(path)) {
WebBrowser browser = browserManager.findBrowserById(path);
diff --git a/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java b/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java
index 5cd056f869d9..3d0451d71b3d 100644
--- a/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java
+++ b/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java
@@ -18,6 +18,10 @@ package com.intellij.ide.browsers.impl;
import com.intellij.ide.browsers.OpenInBrowserRequest;
import com.intellij.ide.browsers.WebBrowserService;
import com.intellij.ide.browsers.WebBrowserUrlProvider;
+import com.intellij.lang.Language;
+import com.intellij.lang.html.HTMLLanguage;
+import com.intellij.lang.xhtml.XHTMLLanguage;
+import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.impl.http.HttpVirtualFile;
@@ -34,6 +38,11 @@ import java.util.Collection;
import java.util.Collections;
public class WebBrowserServiceImpl extends WebBrowserService {
+ public static boolean isHtmlOrXmlFile(@NotNull PsiElement element) {
+ Language language = element.getLanguage();
+ return language == HTMLLanguage.INSTANCE || language == XHTMLLanguage.INSTANCE || language == XMLLanguage.INSTANCE;
+ }
+
@NotNull
@Override
public Collection<Url> getUrlsToOpen(@NotNull OpenInBrowserRequest request, boolean preferLocalUrl) throws WebBrowserUrlProvider.BrowserException {
@@ -42,7 +51,7 @@ public class WebBrowserServiceImpl extends WebBrowserService {
return Collections.singleton(Urls.newFromVirtualFile(virtualFile));
}
- if (!preferLocalUrl || !HtmlUtil.isHtmlFile(request.getFile())) {
+ if (!preferLocalUrl || !isHtmlOrXmlFile(request.getFile())) {
WebBrowserUrlProvider provider = getProvider(request);
if (provider != null) {
if (request.getResult() != null) {
diff --git a/xml/impl/src/com/intellij/psi/formatter/xml/AbstractXmlBlock.java b/xml/impl/src/com/intellij/psi/formatter/xml/AbstractXmlBlock.java
index 6e1b89912814..b2b5b5ff8eee 100644
--- a/xml/impl/src/com/intellij/psi/formatter/xml/AbstractXmlBlock.java
+++ b/xml/impl/src/com/intellij/psi/formatter/xml/AbstractXmlBlock.java
@@ -17,6 +17,7 @@ package com.intellij.psi.formatter.xml;
import com.intellij.formatting.*;
import com.intellij.lang.*;
+import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.util.TextRange;
@@ -464,11 +465,28 @@ public abstract class AbstractXmlBlock extends AbstractBlock {
return myNode.getElementType() == XmlTokenType.XML_CDATA_END;
}
- public static boolean containsWhiteSpacesOnly(ASTNode node) {
- WhiteSpaceFormattingStrategy strategy = WhiteSpaceFormattingStrategyFactory.getStrategy(node.getPsi().getLanguage());
- String nodeText = node.getText();
- int length = nodeText.length();
- return strategy.check(nodeText, 0, length) >= length;
+ public static boolean containsWhiteSpacesOnly(@NotNull ASTNode node) {
+ PsiElement psiElement = node.getPsi();
+ if (psiElement instanceof PsiWhiteSpace) return true;
+ Language nodeLang = psiElement.getLanguage();
+ if (!nodeLang.isKindOf(XMLLanguage.INSTANCE) ||
+ isTextOnlyNode(node) ||
+ node.getElementType() == XmlElementType.XML_PROLOG) {
+ WhiteSpaceFormattingStrategy strategy = WhiteSpaceFormattingStrategyFactory.getStrategy(nodeLang);
+ int length = node.getTextLength();
+ return strategy.check(node.getChars(), 0, length) >= length;
+ }
+ return false;
+ }
+
+ private static boolean isTextOnlyNode(@NotNull ASTNode node) {
+ if (node.getPsi() instanceof XmlText) return true;
+ ASTNode firstChild = node.getFirstChildNode();
+ ASTNode lastChild = node.getLastChildNode();
+ if (firstChild != null && firstChild == lastChild && firstChild.getPsi() instanceof XmlText) {
+ return true;
+ }
+ return false;
}
}
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form
new file mode 100644
index 000000000000..3aa78b3be81c
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.form
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.builtInWebServer.BuiltInServerConfigurableUi">
+ <grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="1077" height="321"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="696ad" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/XmlBundle" key="setting.value.builtin.server.port.label"/>
+ </properties>
+ </component>
+ <component id="e47e0" class="com.intellij.ui.PortField" binding="builtInServerPort">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="33218" class="javax.swing.JCheckBox" binding="builtInServerAvailableExternallyCheckBox" default-binding="true">
+ <constraints>
+ <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/XmlBundle" key="setting.value.can.accept.external.connections"/>
+ </properties>
+ </component>
+ <vspacer id="c36c4">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="3" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ </children>
+ </grid>
+</form>
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java
new file mode 100644
index 000000000000..edbef65543af
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerConfigurableUi.java
@@ -0,0 +1,47 @@
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.options.ConfigurableUi;
+import com.intellij.ui.PortField;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+class BuiltInServerConfigurableUi implements ConfigurableUi<BuiltInServerOptions> {
+ private JPanel mainPanel;
+
+ private PortField builtInServerPort;
+ private JCheckBox builtInServerAvailableExternallyCheckBox;
+
+ public BuiltInServerConfigurableUi() {
+ builtInServerPort.setMin(1024);
+ }
+
+ @Override
+ @NotNull
+ public JComponent getComponent() {
+ return mainPanel;
+ }
+
+ @Override
+ public boolean isModified(@NotNull BuiltInServerOptions settings) {
+ return builtInServerPort.getNumber() != settings.builtInServerPort ||
+ builtInServerAvailableExternallyCheckBox.isSelected() != settings.builtInServerAvailableExternally;
+ }
+
+ @Override
+ public void apply(@NotNull BuiltInServerOptions settings) {
+ boolean builtInServerPortChanged = settings.builtInServerPort != builtInServerPort.getNumber() || settings.builtInServerAvailableExternally != builtInServerAvailableExternallyCheckBox.isSelected();
+ if (builtInServerPortChanged) {
+ settings.builtInServerPort = builtInServerPort.getNumber();
+ settings.builtInServerAvailableExternally = builtInServerAvailableExternallyCheckBox.isSelected();
+
+ BuiltInServerOptions.onBuiltInServerPortChanged();
+ }
+ }
+
+ @Override
+ public void reset(@NotNull BuiltInServerOptions settings) {
+ builtInServerPort.setNumber(settings.builtInServerPort);
+ builtInServerAvailableExternallyCheckBox.setSelected(settings.builtInServerAvailableExternally);
+ }
+}
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java
new file mode 100644
index 000000000000..5eb65c1f9cbc
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInServerOptions.java
@@ -0,0 +1,116 @@
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationDisplayType;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.SimpleConfigurable;
+import com.intellij.openapi.util.Getter;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import com.intellij.util.xmlb.annotations.Attribute;
+import com.intellij.xdebugger.settings.DebuggerConfigurableProvider;
+import com.intellij.xdebugger.settings.DebuggerSettingsCategory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.ide.BuiltInServerManager;
+import org.jetbrains.ide.CustomPortServerManager;
+import org.jetbrains.io.CustomPortServerManagerBase;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+
+@State(
+ name = "BuiltInServerOptions",
+ storages = {
+ @Storage(
+ file = StoragePathMacros.APP_CONFIG + "/other.xml"
+ )}
+)
+public class BuiltInServerOptions implements PersistentStateComponent<BuiltInServerOptions>, ExportableComponent, Getter<BuiltInServerOptions> {
+ @Attribute
+ public int builtInServerPort = 63342;
+ @Attribute
+ public boolean builtInServerAvailableExternally = false;
+
+ public static BuiltInServerOptions getInstance() {
+ return ServiceManager.getService(BuiltInServerOptions.class);
+ }
+
+ @Override
+ public BuiltInServerOptions get() {
+ return this;
+ }
+
+ static final class BuiltInServerDebuggerConfigurableProvider extends DebuggerConfigurableProvider {
+ @NotNull
+ @Override
+ public Collection<? extends Configurable> getConfigurables(@NotNull DebuggerSettingsCategory category) {
+ if (category == DebuggerSettingsCategory.GENERAL) {
+ return Collections.singletonList(SimpleConfigurable.create("builtInServer", "", BuiltInServerConfigurableUi.class, getInstance()));
+ }
+ return Collections.emptyList();
+ }
+ }
+
+ @NotNull
+ @Override
+ public File[] getExportFiles() {
+ return new File[]{PathManager.getOptionsFile("other")};
+ }
+
+ @NotNull
+ @Override
+ public String getPresentableName() {
+ return "Built-in server";
+ }
+
+ @Nullable
+ @Override
+ public BuiltInServerOptions getState() {
+ return this;
+ }
+
+ @Override
+ public void loadState(BuiltInServerOptions state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+
+ public int getEffectiveBuiltInServerPort() {
+ MyCustomPortServerManager portServerManager = CustomPortServerManager.EP_NAME.findExtension(MyCustomPortServerManager.class);
+ if (!portServerManager.isBound()) {
+ return BuiltInServerManager.getInstance().getPort();
+ }
+ return builtInServerPort;
+ }
+
+ public static final class MyCustomPortServerManager extends CustomPortServerManagerBase {
+ @Override
+ public void cannotBind(Exception e, int port) {
+ String groupDisplayId = "Built-in Web Server";
+ Notifications.Bus.register(groupDisplayId, NotificationDisplayType.STICKY_BALLOON);
+ new Notification(groupDisplayId, "Built-in HTTP server on custom port " + port + " disabled",
+ "Cannot start built-in HTTP server on custom port " + port + ". " +
+ "Please ensure that port is free (or check your firewall settings) and restart " + ApplicationNamesInfo.getInstance().getFullProductName(),
+ NotificationType.ERROR).notify(null);
+ }
+
+ @Override
+ public int getPort() {
+ return getInstance().builtInServerPort;
+ }
+
+ @Override
+ public boolean isAvailableExternally() {
+ return getInstance().builtInServerAvailableExternally;
+ }
+ }
+
+ public static void onBuiltInServerPortChanged() {
+ CustomPortServerManager.EP_NAME.findExtension(MyCustomPortServerManager.class).portChanged();
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
new file mode 100644
index 000000000000..f58d42587cab
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -0,0 +1,78 @@
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.ide.browsers.OpenInBrowserRequest;
+import com.intellij.ide.browsers.WebBrowserUrlProvider;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.testFramework.LightVirtualFile;
+import com.intellij.util.Url;
+import com.intellij.util.Urls;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.xml.util.HtmlUtil;
+import org.jetbrains.ide.BuiltInServerManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implements DumbAware {
+ @NotNull
+ public static List<Url> getUrls(@NotNull VirtualFile file, @NotNull Project project, @Nullable String currentAuthority) {
+ if (currentAuthority != null && !compareAuthority(currentAuthority)) {
+ return Collections.emptyList();
+ }
+
+ String path = WebServerPathToFileManager.getInstance(project).getPath(file);
+ if (path == null) {
+ return Collections.emptyList();
+ }
+
+ int effectiveBuiltInServerPort = BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort();
+ Url url = Urls.newHttpUrl(currentAuthority == null ? "localhost:" + effectiveBuiltInServerPort : currentAuthority, '/' + project.getName() + '/' + path);
+ int defaultPort = BuiltInServerManager.getInstance().getPort();
+ if (currentAuthority != null || defaultPort == effectiveBuiltInServerPort) {
+ return Collections.singletonList(url);
+ }
+ return Arrays.asList(url, Urls.newHttpUrl("localhost:" + defaultPort, '/' + project.getName() + '/' + path));
+ }
+
+ public static boolean compareAuthority(@Nullable String currentAuthority) {
+ if (currentAuthority == null) {
+ return false;
+ }
+
+ int portIndex = currentAuthority.indexOf(':');
+ if (portIndex < 0) {
+ return false;
+ }
+
+ String host = currentAuthority.substring(0, portIndex);
+ if (!BuiltInWebServer.isOwnHostName(host)) {
+ return false;
+ }
+
+ int port = StringUtil.parseInt(currentAuthority.substring(portIndex + 1), -1);
+ return port == BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort() ||
+ port == BuiltInServerManager.getInstance().getPort();
+ }
+
+ @Override
+ public boolean canHandleElement(@NotNull OpenInBrowserRequest request) {
+ return request.getFile().getViewProvider().isPhysical() && !(request.getVirtualFile() instanceof LightVirtualFile) && isMyLanguage(request.getFile());
+ }
+
+ protected boolean isMyLanguage(PsiFile psiFile) {
+ return HtmlUtil.isHtmlFile(psiFile);
+ }
+
+ @Nullable
+ @Override
+ protected Url getUrl(@NotNull OpenInBrowserRequest request, @NotNull VirtualFile virtualFile) throws BrowserException {
+ return ContainerUtil.getFirstItem(getUrls(virtualFile, request.getProject(), null));
+ }
+}
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java
new file mode 100644
index 000000000000..2dc24ba01706
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.util.SystemInfoRt;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.UriUtil;
+import com.intellij.util.io.URLUtil;
+import com.intellij.util.net.NetUtils;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.ide.HttpRequestHandler;
+import org.jetbrains.io.FileResponses;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import static org.jetbrains.io.Responses.sendOptionsResponse;
+import static org.jetbrains.io.Responses.sendStatus;
+
+public final class BuiltInWebServer extends HttpRequestHandler {
+ static final Logger LOG = Logger.getInstance(BuiltInWebServer.class);
+
+ @Nullable
+ public static VirtualFile findIndexFile(@NotNull VirtualFile basedir) {
+ VirtualFile[] children = basedir.getChildren();
+ if (children == null || children.length == 0) {
+ return null;
+ }
+
+ for (String indexNamePrefix : new String[]{"index.", "default."}) {
+ VirtualFile index = null;
+ String preferredName = indexNamePrefix + "html";
+ for (VirtualFile child : children) {
+ if (!child.isDirectory()) {
+ String name = child.getName();
+ if (name.equals(preferredName)) {
+ return child;
+ }
+ else if (index == null && name.startsWith(indexNamePrefix)) {
+ index = child;
+ }
+ }
+ }
+ if (index != null) {
+ return index;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isSupported(@NotNull FullHttpRequest request) {
+ return super.isSupported(request) || request.method() == HttpMethod.POST || request.method() == HttpMethod.OPTIONS;
+ }
+
+ @Override
+ public boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
+ if (request.method() == HttpMethod.OPTIONS) {
+ sendOptionsResponse("GET, POST, HEAD, OPTIONS", request, context);
+ return true;
+ }
+
+ String host = HttpHeaders.getHost(request);
+ if (StringUtil.isEmpty(host)) {
+ return false;
+ }
+
+ int portIndex = host.indexOf(':');
+ if (portIndex > 0) {
+ host = host.substring(0, portIndex);
+ }
+
+ String projectName;
+ boolean isIpv6 = host.charAt(0) == '[' && host.length() > 2 && host.charAt(host.length() - 1) == ']';
+ if (isIpv6) {
+ host = host.substring(1, host.length() - 1);
+ }
+
+ if (isIpv6 || Character.digit(host.charAt(0), 10) != -1 || host.charAt(0) == ':' || isOwnHostName(host)) {
+ if (urlDecoder.path().length() < 2) {
+ return false;
+ }
+ projectName = null;
+ }
+ else {
+ projectName = host;
+ }
+ return doProcess(request, context.channel(), projectName);
+ }
+
+ public static boolean isOwnHostName(@NotNull String host) {
+ if (NetUtils.isLocalhost(host)) {
+ return true;
+ }
+
+ try {
+ InetAddress address = InetAddress.getByName(host);
+ if (host.equals(address.getHostAddress()) || host.equalsIgnoreCase(address.getCanonicalHostName())) {
+ return true;
+ }
+
+ String localHostName = InetAddress.getLocalHost().getHostName();
+ // WEB-8889
+ // develar.local is own host name: develar. equals to "develar.labs.intellij.net" (canonical host name)
+ return localHostName.equalsIgnoreCase(host) ||
+ (host.endsWith(".local") && localHostName.regionMatches(true, 0, host, 0, host.length() - ".local".length()));
+ }
+ catch (UnknownHostException ignored) {
+ return false;
+ }
+ }
+
+ private static boolean doProcess(@NotNull FullHttpRequest request, @NotNull Channel channel, @Nullable String projectName) {
+ final String decodedPath = URLUtil.unescapePercentSequences(UriUtil.trimParameters(request.uri()));
+ int offset;
+ boolean emptyPath;
+ boolean isCustomHost = projectName != null;
+ if (isCustomHost) {
+ // host mapped to us
+ offset = 0;
+ emptyPath = decodedPath.isEmpty();
+ }
+ else {
+ offset = decodedPath.indexOf('/', 1);
+ projectName = decodedPath.substring(1, offset == -1 ? decodedPath.length() : offset);
+ emptyPath = offset == -1;
+ }
+
+ Project project = findProject(projectName, isCustomHost);
+ if (project == null) {
+ return false;
+ }
+
+ if (emptyPath) {
+ if (!SystemInfoRt.isFileSystemCaseSensitive) {
+ // may be passed path is not correct
+ projectName = project.getName();
+ }
+
+ // we must redirect "jsdebug" to "jsdebug/" as nginx does, otherwise browser will treat it as file instead of directory, so, relative path will not work
+ WebServerPathHandler.redirectToDirectory(request, channel, projectName);
+ return true;
+ }
+
+ final String path = FileUtil.toCanonicalPath(decodedPath.substring(offset + 1), '/');
+ LOG.assertTrue(path != null);
+
+ for (WebServerPathHandler pathHandler : WebServerPathHandler.EP_NAME.getExtensions()) {
+ try {
+ if (pathHandler.process(path, project, request, channel, projectName, decodedPath, isCustomHost)) {
+ return true;
+ }
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ return false;
+ }
+
+ static final class StaticFileHandler extends WebServerFileHandler {
+ @Override
+ public boolean process(@NotNull VirtualFile file,
+ @NotNull CharSequence canonicalRequestPath,
+ @NotNull Project project,
+ @NotNull FullHttpRequest request,
+ @NotNull Channel channel) throws IOException {
+ File ioFile = VfsUtilCore.virtualToIoFile(file);
+ if (hasAccess(ioFile)) {
+ FileResponses.sendFile(request, channel, ioFile);
+ }
+ else {
+ sendStatus(HttpResponseStatus.FORBIDDEN, channel, request);
+ }
+ return true;
+ }
+
+ private static boolean hasAccess(File result) {
+ // deny access to .htaccess files
+ return !result.isDirectory() && result.canRead() && !(result.isHidden() || result.getName().startsWith(".ht"));
+ }
+ }
+
+ @Nullable
+ private static Project findProject(String projectName, boolean isCustomHost) {
+ // user can rename project directory, so, we should support this case - find project by base directory name
+ Project candidateByDirectoryName = null;
+ for (Project project : ProjectManager.getInstance().getOpenProjects()) {
+ String name = project.getName();
+ // domain name is case-insensitive
+ if (!project.isDisposed() && ((isCustomHost || !SystemInfoRt.isFileSystemCaseSensitive) ? projectName.equalsIgnoreCase(name) : projectName.equals(name))) {
+ return project;
+ }
+
+ if (candidateByDirectoryName == null && compareNameAndProjectBasePath(projectName, project)) {
+ candidateByDirectoryName = project;
+ }
+ }
+ return candidateByDirectoryName;
+ }
+
+ public static boolean compareNameAndProjectBasePath(String projectName, Project project) {
+ String basePath = project.getBasePath();
+ return basePath != null && basePath.length() > projectName.length() && basePath.endsWith(projectName) && basePath.charAt(basePath.length() - projectName.length() - 1) == '/';
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.java b/xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.java
new file mode 100644
index 000000000000..1b4acb07556f
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.io.Responses;
+
+final class DefaultWebServerPathHandler extends WebServerPathHandler {
+ @Override
+ public boolean process(@NotNull String path,
+ @NotNull Project project,
+ @NotNull FullHttpRequest request,
+ @NotNull Channel channel,
+ @Nullable String projectName,
+ @NotNull String decodedRawPath,
+ boolean isCustomHost) {
+ WebServerPathToFileManager pathToFileManager = WebServerPathToFileManager.getInstance(project);
+ VirtualFile result = pathToFileManager.pathToFileCache.getIfPresent(path);
+ boolean indexUsed = false;
+ if (result == null || !result.isValid()) {
+ result = pathToFileManager.findByRelativePath(project, path);
+ if (result == null) {
+ if (path.isEmpty()) {
+ Responses.sendStatus(HttpResponseStatus.NOT_FOUND, channel, "Index file doesn't exist.", request);
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else if (result.isDirectory()) {
+ if (!endsWithSlash(decodedRawPath)) {
+ redirectToDirectory(request, channel, isCustomHost ? path : (projectName + '/' + path));
+ return true;
+ }
+
+ result = BuiltInWebServer.findIndexFile(result);
+ if (result == null) {
+ Responses.sendStatus(HttpResponseStatus.NOT_FOUND, channel, "Index file doesn't exist.", request);
+ return true;
+ }
+ indexUsed = true;
+ }
+
+ pathToFileManager.pathToFileCache.put(path, result);
+ }
+ else if (!path.endsWith(result.getName())) {
+ if (endsWithSlash(decodedRawPath)) {
+ indexUsed = true;
+ }
+ else {
+ // FallbackResource feature in action, /login requested, /index.php retrieved, we must not redirect /login to /login/
+ if (path.endsWith(result.getParent().getName())) {
+ redirectToDirectory(request, channel, isCustomHost ? path : (projectName + '/' + path));
+ return true;
+ }
+ }
+ }
+
+ StringBuilder canonicalRequestPath = new StringBuilder();
+ canonicalRequestPath.append('/');
+ if (!isCustomHost) {
+ canonicalRequestPath.append(projectName).append('/');
+ }
+ canonicalRequestPath.append(path);
+ if (indexUsed) {
+ canonicalRequestPath.append('/').append(result.getName());
+ }
+
+ for (WebServerFileHandler fileHandler : WebServerFileHandler.EP_NAME.getExtensions()) {
+ try {
+ if (fileHandler.process(result, canonicalRequestPath, project, request, channel)) {
+ return true;
+ }
+ }
+ catch (Throwable e) {
+ BuiltInWebServer.LOG.error(e);
+ }
+ }
+
+ return false;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerRootsProvider.java b/xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerRootsProvider.java
new file mode 100644
index 000000000000..b5621295957d
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/DefaultWebServerRootsProvider.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.application.AccessToken;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.PairFunction;
+import com.intellij.util.PlatformUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class DefaultWebServerRootsProvider extends WebServerRootsProvider {
+ @Nullable
+ @Override
+ public PathInfo resolve(@NotNull String path, @NotNull Project project) {
+ PairFunction<String, VirtualFile, VirtualFile> resolver;
+ if (PlatformUtils.isIntelliJ()) {
+ int index = path.indexOf('/');
+ if (index > 0 && !path.regionMatches(!SystemInfo.isFileSystemCaseSensitive, 0, project.getName(), 0, index)) {
+ String moduleName = path.substring(0, index);
+ AccessToken token = ReadAction.start();
+ Module module;
+ try {
+ module = ModuleManager.getInstance(project).findModuleByName(moduleName);
+ }
+ finally {
+ token.finish();
+ }
+
+ if (module != null && !module.isDisposed()) {
+ path = path.substring(index + 1);
+ resolver = WebServerPathToFileManager.getInstance(project).getResolver(path);
+
+ ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
+ PathInfo result = resolve(path, moduleRootManager.getSourceRoots(), resolver, moduleName);
+ if (result == null) {
+ result = resolve(path, moduleRootManager.getContentRoots(), resolver, moduleName);
+ }
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ }
+
+ Module[] modules;
+ AccessToken token = ReadAction.start();
+ try {
+ modules = ModuleManager.getInstance(project).getModules();
+ }
+ finally {
+ token.finish();
+ }
+
+ resolver = WebServerPathToFileManager.getInstance(project).getResolver(path);
+ PathInfo result = findByRelativePath(project, path, modules, true, resolver);
+ if (result == null) {
+ // let's find in content roots
+ return findByRelativePath(project, path, modules, false, resolver);
+ }
+ else {
+ return result;
+ }
+ }
+
+ @Nullable
+ @Override
+ public PathInfo getRoot(@NotNull VirtualFile file, @NotNull Project project) {
+ AccessToken token = ReadAction.start();
+ try {
+ VirtualFile root = null;
+ ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ if (fileIndex.isInSourceContent(file)) {
+ root = fileIndex.getSourceRootForFile(file);
+ }
+ else if (fileIndex.isInContent(file)) {
+ root = fileIndex.getContentRootForFile(file);
+ }
+ else if (fileIndex.isInLibraryClasses(file)) {
+ root = fileIndex.getClassRootForFile(file);
+ }
+ assert root != null : file.getPresentableUrl();
+ return new PathInfo(file, root, getModuleNameQualifier(project, fileIndex.getModuleForFile(file)));
+ }
+ finally {
+ token.finish();
+ }
+ }
+
+ @Nullable
+ private static String getModuleNameQualifier(@NotNull Project project, @Nullable Module module) {
+ if (module != null &&
+ PlatformUtils.isIntelliJ() &&
+ !(module.getName().equalsIgnoreCase(project.getName()) || BuiltInWebServer.compareNameAndProjectBasePath(module.getName(), project))) {
+ return module.getName();
+ }
+ return null;
+ }
+
+ @Nullable
+ private static PathInfo resolve(@NotNull String path, @NotNull VirtualFile[] roots, @NotNull PairFunction<String, VirtualFile, VirtualFile> resolver, @Nullable String moduleName) {
+ for (VirtualFile root : roots) {
+ VirtualFile file = resolver.fun(path, root);
+ if (file != null) {
+ return new PathInfo(file, root, moduleName);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static PathInfo findByRelativePath(@NotNull Project project,
+ @NotNull String path,
+ @NotNull Module[] modules,
+ boolean inSourceRoot,
+ @NotNull PairFunction<String, VirtualFile, VirtualFile> resolver) {
+ for (Module module : modules) {
+ if (!module.isDisposed()) {
+ ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
+ PathInfo result = resolve(path, inSourceRoot ? moduleRootManager.getSourceRoots() : moduleRootManager.getContentRoots(), resolver, null);
+ if (result != null) {
+ result.moduleName = getModuleNameQualifier(project, module);
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/PathInfo.java b/xml/impl/src/org/jetbrains/builtInWebServer/PathInfo.java
new file mode 100644
index 000000000000..b55b16be84b2
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/PathInfo.java
@@ -0,0 +1,47 @@
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PathInfo {
+ private final VirtualFile child;
+ private final VirtualFile root;
+ String moduleName;
+
+ private String computedPath;
+
+ public PathInfo(@NotNull VirtualFile child, @NotNull VirtualFile root, @Nullable String moduleName) {
+ this.child = child;
+ this.root = root;
+ this.moduleName = moduleName;
+ }
+
+ public PathInfo(@NotNull VirtualFile child, @NotNull VirtualFile root) {
+ this(child, root, null);
+ }
+
+ @NotNull
+ public VirtualFile getChild() {
+ return child;
+ }
+
+ @NotNull
+ public VirtualFile getRoot() {
+ return root;
+ }
+
+ @Nullable
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ @NotNull
+ public String getPath() {
+ if (computedPath == null) {
+ computedPath = (moduleName == null ? "" : moduleName + '/') + VfsUtilCore.getRelativePath(child, root, '/');
+ }
+ return computedPath;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/PrefixlessWebServerRootsProvider.java b/xml/impl/src/org/jetbrains/builtInWebServer/PrefixlessWebServerRootsProvider.java
new file mode 100644
index 000000000000..145fdedb350e
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/PrefixlessWebServerRootsProvider.java
@@ -0,0 +1,18 @@
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.PairFunction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class PrefixlessWebServerRootsProvider extends WebServerRootsProvider {
+ @Nullable
+ @Override
+ public final PathInfo resolve(@NotNull String path, @NotNull Project project) {
+ return resolve(path, project, WebServerPathToFileManager.getInstance(project).getResolver(path));
+ }
+
+ @Nullable
+ public abstract PathInfo resolve(@NotNull String path, @NotNull Project project, @NotNull PairFunction<String, VirtualFile, VirtualFile> resolver);
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/WebServerFileHandler.java b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerFileHandler.java
new file mode 100644
index 000000000000..9035af6c52cc
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerFileHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public abstract class WebServerFileHandler {
+ static final ExtensionPointName<WebServerFileHandler> EP_NAME = ExtensionPointName.create("org.jetbrains.webServerFileHandler");
+
+ public abstract boolean process(@NotNull VirtualFile file,
+ @NotNull CharSequence canonicalRequestPath,
+ @NotNull Project project,
+ @NotNull FullHttpRequest request,
+ @NotNull Channel channel) throws IOException;
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandler.java b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandler.java
new file mode 100644
index 000000000000..807c3b31b49a
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.io.Responses;
+
+import java.net.URI;
+
+/**
+ * By default {@link WebServerPathToFileManager} will be used to map request to file.
+ * If file physically exists in the file system, you must use {@link WebServerRootsProvider}.
+ *
+ * Consider to extend {@link WebServerPathHandlerAdapter} instead of implement low-level {@link #process(String, com.intellij.openapi.project.Project, io.netty.handler.codec.http.FullHttpRequest, io.netty.channel.Channel, String, String, boolean)}
+ */
+public abstract class WebServerPathHandler {
+ static final ExtensionPointName<WebServerPathHandler> EP_NAME = ExtensionPointName.create("org.jetbrains.webServerPathHandler");
+
+ public abstract boolean process(@NotNull String path,
+ @NotNull Project project,
+ @NotNull FullHttpRequest request,
+ @NotNull Channel channel,
+ @Nullable String projectName,
+ @NotNull String decodedRawPath,
+ boolean isCustomHost);
+
+ protected static void redirectToDirectory(@NotNull HttpRequest request, @NotNull Channel channel, @NotNull String path) {
+ FullHttpResponse response = Responses.response(HttpResponseStatus.MOVED_PERMANENTLY);
+ URI url = VfsUtil.toUri("http://" + HttpHeaders.getHost(request) + '/' + path + '/');
+ BuiltInWebServer.LOG.assertTrue(url != null);
+ response.headers().add(HttpHeaders.Names.LOCATION, url.toASCIIString());
+ Responses.send(response, channel, request);
+ }
+
+ protected static boolean endsWithSlash(@NotNull String path) {
+ return path.charAt(path.length() - 1) == '/';
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandlerAdapter.java b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandlerAdapter.java
new file mode 100644
index 000000000000..a1f1e1790195
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathHandlerAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.project.Project;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class WebServerPathHandlerAdapter extends WebServerPathHandler {
+ protected abstract boolean process(@NotNull String path, @NotNull Project project, @NotNull FullHttpRequest request, @NotNull Channel channel);
+
+ @Override
+ public final boolean process(@NotNull String path,
+ @NotNull Project project,
+ @NotNull FullHttpRequest request,
+ @NotNull Channel channel,
+ @Nullable String projectName,
+ @NotNull String decodedRawPath,
+ boolean isCustomHost) {
+ return process(path, project, request, channel);
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathToFileManager.java b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathToFileManager.java
new file mode 100644
index 000000000000..987fdcd3a139
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerPathToFileManager.java
@@ -0,0 +1,142 @@
+package org.jetbrains.builtInWebServer;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.intellij.ProjectTopics;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModuleRootAdapter;
+import com.intellij.openapi.roots.ModuleRootEvent;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.vfs.newvfs.BulkFileListener;
+import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
+import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
+import com.intellij.util.PairFunction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implement {@link WebServerRootsProvider} to add your provider
+ */
+public class WebServerPathToFileManager {
+ private static final PairFunction<String, VirtualFile, VirtualFile> RELATIVE_PATH_RESOLVER = new PairFunction<String, VirtualFile, VirtualFile>() {
+ @Nullable
+ @Override
+ public VirtualFile fun(String path, VirtualFile parent) {
+ return parent.findFileByRelativePath(path);
+ }
+ };
+
+ private static final PairFunction<String, VirtualFile, VirtualFile> EMPTY_PATH_RESOLVER = new PairFunction<String, VirtualFile, VirtualFile>() {
+ @Nullable
+ @Override
+ public VirtualFile fun(String path, VirtualFile parent) {
+ return BuiltInWebServer.findIndexFile(parent);
+ }
+ };
+
+ private final Project project;
+
+ final Cache<String, VirtualFile> pathToFileCache = CacheBuilder.newBuilder().maximumSize(512).expireAfterAccess(10, TimeUnit.MINUTES).build();
+ // time to expire should be greater than pathToFileCache
+ private final Cache<VirtualFile, PathInfo> fileToRoot = CacheBuilder.newBuilder().maximumSize(512).expireAfterAccess(11, TimeUnit.MINUTES).build();
+
+ public static WebServerPathToFileManager getInstance(@NotNull Project project) {
+ return ServiceManager.getService(project, WebServerPathToFileManager.class);
+ }
+
+ public WebServerPathToFileManager(@NotNull Application application, @NotNull Project project) {
+ this.project = project;
+ application.getMessageBus().connect(project).subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
+ @Override
+ public void after(@NotNull List<? extends VFileEvent> events) {
+ for (VFileEvent event : events) {
+ if (event instanceof VFileContentChangeEvent) {
+ VirtualFile file = ((VFileContentChangeEvent)event).getFile();
+ for (WebServerRootsProvider rootsProvider : WebServerRootsProvider.EP_NAME.getExtensions()) {
+ if (rootsProvider.isClearCacheOnFileContentChanged(file)) {
+ clearCache();
+ break;
+ }
+ }
+ }
+ else {
+ clearCache();
+ break;
+ }
+ }
+ }
+ });
+ project.getMessageBus().connect().subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
+ @Override
+ public void rootsChanged(ModuleRootEvent event) {
+ clearCache();
+ }
+ });
+ }
+
+ private void clearCache() {
+ pathToFileCache.invalidateAll();
+ fileToRoot.invalidateAll();
+ }
+
+ @Nullable
+ public VirtualFile get(@NotNull String path) {
+ return get(path, true);
+ }
+
+ @Nullable
+ public VirtualFile get(@NotNull String path, boolean cacheResult) {
+ VirtualFile result = pathToFileCache.getIfPresent(path);
+ if (result == null || !result.isValid()) {
+ result = findByRelativePath(project, path);
+ if (cacheResult && result != null && result.isValid()) {
+ pathToFileCache.put(path, result);
+ }
+ }
+ return result;
+ }
+
+ @Nullable
+ public String getPath(@NotNull VirtualFile file) {
+ PathInfo pathInfo = getRoot(file);
+ return pathInfo == null ? null : pathInfo.getPath();
+ }
+
+ @Nullable
+ public PathInfo getRoot(@NotNull VirtualFile child) {
+ PathInfo result = fileToRoot.getIfPresent(child);
+ if (result == null) {
+ for (WebServerRootsProvider rootsProvider : WebServerRootsProvider.EP_NAME.getExtensions()) {
+ result = rootsProvider.getRoot(child, project);
+ if (result != null) {
+ fileToRoot.put(child, result);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Nullable
+ VirtualFile findByRelativePath(@NotNull Project project, @NotNull String path) {
+ for (WebServerRootsProvider rootsProvider : WebServerRootsProvider.EP_NAME.getExtensions()) {
+ PathInfo result = rootsProvider.resolve(path, project);
+ if (result != null) {
+ fileToRoot.put(result.getChild(), result);
+ return result.getChild();
+ }
+ }
+ return null;
+ }
+
+ @NotNull
+ public PairFunction<String, VirtualFile, VirtualFile> getResolver(@NotNull String path) {
+ return path.isEmpty() ? EMPTY_PATH_RESOLVER : RELATIVE_PATH_RESOLVER;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/WebServerRootsProvider.java b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerRootsProvider.java
new file mode 100644
index 000000000000..bb8972591ac5
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/WebServerRootsProvider.java
@@ -0,0 +1,21 @@
+package org.jetbrains.builtInWebServer;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class WebServerRootsProvider {
+ static final ExtensionPointName<WebServerRootsProvider> EP_NAME = ExtensionPointName.create("org.jetbrains.webServerRootsProvider");
+
+ @Nullable
+ public abstract PathInfo resolve(@NotNull String path, @NotNull Project project);
+
+ @Nullable
+ public abstract PathInfo getRoot(@NotNull VirtualFile file, @NotNull Project project);
+
+ public boolean isClearCacheOnFileContentChanged(@NotNull VirtualFile file) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java
new file mode 100644
index 000000000000..d24078221b30
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java
@@ -0,0 +1,108 @@
+package org.jetbrains.io.fastCgi;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.util.text.StringUtilRt;
+import com.intellij.util.containers.ConcurrentIntObjectMap;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.*;
+import org.jetbrains.io.Responses;
+import org.jetbrains.io.SimpleChannelInboundHandlerAdapter;
+
+import static org.jetbrains.io.fastCgi.FastCgiService.LOG;
+
+@ChannelHandler.Sharable
+public class FastCgiChannelHandler extends SimpleChannelInboundHandlerAdapter<FastCgiResponse> {
+ private final ConcurrentIntObjectMap<Channel> requestToChannel;
+
+ public FastCgiChannelHandler(ConcurrentIntObjectMap<Channel> channel) {
+ requestToChannel = channel;
+ }
+
+ @Override
+ protected void messageReceived(ChannelHandlerContext context, FastCgiResponse response) throws Exception {
+ ByteBuf buffer = response.getData();
+ Channel channel = requestToChannel.remove(response.getId());
+ if (channel == null || !channel.isActive()) {
+ if (buffer != null) {
+ buffer.release();
+ }
+ return;
+ }
+
+ if (buffer == null) {
+ Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel);
+ return;
+ }
+
+ HttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buffer);
+ try {
+ parseHeaders(httpResponse, buffer);
+ Responses.addServer(httpResponse);
+ if (!HttpHeaders.isContentLengthSet(httpResponse)) {
+ HttpHeaders.setContentLength(httpResponse, buffer.readableBytes());
+ }
+ }
+ catch (Throwable e) {
+ buffer.release();
+ Responses.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, channel);
+ LOG.error(e);
+ }
+ channel.writeAndFlush(httpResponse);
+ }
+
+ private static void parseHeaders(HttpResponse response, ByteBuf buffer) {
+ StringBuilder builder = new StringBuilder();
+ while (buffer.isReadable()) {
+ builder.setLength(0);
+
+ String key = null;
+ boolean valueExpected = true;
+ while (true) {
+ int b = buffer.readByte();
+ if (b < 0 || b == '\n') {
+ break;
+ }
+
+ if (b != '\r') {
+ if (valueExpected && b == ':') {
+ valueExpected = false;
+
+ key = builder.toString();
+ builder.setLength(0);
+ skipWhitespace(buffer);
+ }
+ else {
+ builder.append((char)b);
+ }
+ }
+ }
+
+ if (builder.length() == 0) {
+ // end of headers
+ return;
+ }
+
+ // skip standard headers
+ if (StringUtil.isEmpty(key) || StringUtilRt.startsWithIgnoreCase(key, "http") || StringUtilRt.startsWithIgnoreCase(key, "X-Accel-")) {
+ continue;
+ }
+
+ String value = builder.toString();
+ if (key.equalsIgnoreCase("status")) {
+ response.setStatus(HttpResponseStatus.valueOf(Integer.parseInt(value.substring(0, value.indexOf(' ')))));
+ }
+ else if (!(key.startsWith("http") || key.startsWith("HTTP"))) {
+ response.headers().add(key, value);
+ }
+ }
+ }
+
+ private static void skipWhitespace(ByteBuf buffer) {
+ while (buffer.isReadable() && buffer.getByte(buffer.readerIndex()) == ' ') {
+ buffer.skipBytes(1);
+ }
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java
new file mode 100644
index 000000000000..23a85dead1f9
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiConstants.java
@@ -0,0 +1,5 @@
+package org.jetbrains.io.fastCgi;
+
+public final class FastCgiConstants {
+ public static final int HEADER_LENGTH = 8;
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java
new file mode 100644
index 000000000000..1cd7adbef5d4
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java
@@ -0,0 +1,149 @@
+package org.jetbrains.io.fastCgi;
+
+import com.intellij.util.Consumer;
+import gnu.trove.TIntObjectHashMap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.CharsetUtil;
+import org.jetbrains.io.Decoder;
+
+import static org.jetbrains.io.fastCgi.FastCgiService.LOG;
+
+public class FastCgiDecoder extends Decoder {
+ private enum State {
+ HEADER, CONTENT
+ }
+
+ private State state = State.HEADER;
+
+ private enum ProtocolStatus {
+ REQUEST_COMPLETE, CANT_MPX_CONN, OVERLOADED, UNKNOWN_ROLE
+ }
+
+ public static final class RecordType {
+ public static final int END_REQUEST = 3;
+ public static final int STDOUT = 6;
+ public static final int STDERR = 7;
+ }
+
+ private int type;
+ private int id;
+ private int contentLength;
+ private int paddingLength;
+
+ private final TIntObjectHashMap<ByteBuf> dataBuffers = new TIntObjectHashMap<ByteBuf>();
+
+ private final Consumer<String> errorOutputConsumer;
+
+ public FastCgiDecoder(Consumer<String> errorOutputConsumer) {
+ this.errorOutputConsumer = errorOutputConsumer;
+ }
+
+ @Override
+ protected void messageReceived(ChannelHandlerContext context, ByteBuf input) throws Exception {
+ while (true) {
+ switch (state) {
+ case HEADER: {
+ if (paddingLength > 0) {
+ if (input.readableBytes() >= paddingLength) {
+ input.skipBytes(paddingLength);
+ paddingLength = 0;
+ }
+ else {
+ paddingLength -= input.readableBytes();
+ input.skipBytes(input.readableBytes());
+ input.release();
+ return;
+ }
+ }
+
+ ByteBuf buffer = getBufferIfSufficient(input, FastCgiConstants.HEADER_LENGTH, context);
+ if (buffer == null) {
+ input.release();
+ return;
+ }
+
+ decodeHeader(buffer);
+ state = State.CONTENT;
+ }
+
+ case CONTENT: {
+ if (contentLength > 0) {
+ ByteBuf buffer = getBufferIfSufficient(input, contentLength, context);
+ if (buffer == null) {
+ input.release();
+ return;
+ }
+
+ FastCgiResponse response = readContent(buffer);
+ if (response != null) {
+ context.fireChannelRead(response);
+ }
+ }
+ state = State.HEADER;
+ }
+ }
+ }
+ }
+
+ private void decodeHeader(ByteBuf buffer) {
+ buffer.skipBytes(1);
+ type = buffer.readUnsignedByte();
+ id = buffer.readUnsignedShort();
+ contentLength = buffer.readUnsignedShort();
+ paddingLength = buffer.readUnsignedByte();
+ buffer.skipBytes(1);
+ }
+
+ private FastCgiResponse readContent(ByteBuf buffer) {
+ switch (type) {
+ case RecordType.END_REQUEST:
+ int appStatus = buffer.readInt();
+ int protocolStatus = buffer.readUnsignedByte();
+ buffer.skipBytes(3);
+ if (appStatus != 0 || protocolStatus != ProtocolStatus.REQUEST_COMPLETE.ordinal()) {
+ LOG.warn("Protocol status " + protocolStatus);
+ dataBuffers.remove(id);
+ return new FastCgiResponse(id, null);
+ }
+ else if (protocolStatus == ProtocolStatus.REQUEST_COMPLETE.ordinal()) {
+ return new FastCgiResponse(id, dataBuffers.remove(id));
+ }
+ break;
+
+ case RecordType.STDOUT:
+ ByteBuf data = dataBuffers.get(id);
+ ByteBuf sliced = buffer.slice(buffer.readerIndex(), contentLength);
+ if (data == null) {
+ dataBuffers.put(id, sliced);
+ }
+ else if (data instanceof CompositeByteBuf) {
+ ((CompositeByteBuf)data).addComponent(sliced);
+ data.writerIndex(data.writerIndex() + sliced.readableBytes());
+ }
+ else {
+ dataBuffers.put(id, Unpooled.wrappedBuffer(data, sliced));
+ }
+ sliced.retain();
+ buffer.skipBytes(contentLength);
+ break;
+
+ case RecordType.STDERR:
+ try {
+ errorOutputConsumer.consume(buffer.toString(buffer.readerIndex(), contentLength, CharsetUtil.UTF_8));
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ buffer.skipBytes(contentLength);
+ break;
+
+ default:
+ LOG.error("Unknown type " + type);
+ break;
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java
new file mode 100644
index 000000000000..e92d20eebffc
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiRequest.java
@@ -0,0 +1,149 @@
+package org.jetbrains.io.fastCgi;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.util.CharsetUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.builtInWebServer.PathInfo;
+import org.jetbrains.builtInWebServer.WebServerPathToFileManager;
+import org.jetbrains.io.Responses;
+
+import java.net.InetSocketAddress;
+import java.util.Locale;
+import java.util.Map;
+
+public class FastCgiRequest {
+ private static final int PARAMS = 4;
+ private static final int BEGIN_REQUEST = 1;
+ private static final int RESPONDER = 1;
+ private static final int FCGI_KEEP_CONNECTION = 1;
+ private static final int STDIN = 5;
+ private static final int VERSION = 1;
+
+ private final ByteBuf buffer;
+ final int requestId;
+
+ public FastCgiRequest(int requestId, @NotNull ByteBufAllocator allocator) {
+ this.requestId = requestId;
+
+ buffer = allocator.buffer();
+ writeHeader(buffer, BEGIN_REQUEST, FastCgiConstants.HEADER_LENGTH);
+ buffer.writeShort(RESPONDER);
+ buffer.writeByte(FCGI_KEEP_CONNECTION);
+ buffer.writeZero(5);
+ }
+
+ public void writeFileHeaders(@NotNull VirtualFile file, @NotNull Project project, @NotNull CharSequence canonicalRequestPath) {
+ PathInfo root = WebServerPathToFileManager.getInstance(project).getRoot(file);
+ FastCgiService.LOG.assertTrue(root != null);
+ addHeader("DOCUMENT_ROOT", root.getRoot().getPath());
+ addHeader("SCRIPT_FILENAME", file.getPath());
+ addHeader("SCRIPT_NAME", canonicalRequestPath);
+ }
+
+ public final void addHeader(@NotNull String key, @Nullable CharSequence value) {
+ if (value == null) {
+ return;
+ }
+
+ int keyLength = key.length();
+ int valLength = value.length();
+ writeHeader(buffer, PARAMS, keyLength + valLength + (keyLength < 0x80 ? 1 : 4) + (valLength < 0x80 ? 1 : 4));
+
+ if (keyLength < 0x80) {
+ buffer.writeByte(keyLength);
+ }
+ else {
+ buffer.writeByte(0x80 | (keyLength >> 24));
+ buffer.writeByte(keyLength >> 16);
+ buffer.writeByte(keyLength >> 8);
+ buffer.writeByte(keyLength);
+ }
+
+ if (valLength < 0x80) {
+ buffer.writeByte(valLength);
+ }
+ else {
+ buffer.writeByte(0x80 | (valLength >> 24));
+ buffer.writeByte(valLength >> 16);
+ buffer.writeByte(valLength >> 8);
+ buffer.writeByte(valLength);
+ }
+
+ buffer.writeBytes(key.getBytes(CharsetUtil.US_ASCII));
+ buffer.writeBytes(Unpooled.copiedBuffer(value, CharsetUtil.UTF_8));
+ }
+
+ public void writeHeaders(FullHttpRequest request, Channel clientChannel) {
+ addHeader("REQUEST_URI", request.uri());
+ addHeader("REQUEST_METHOD", request.method().name());
+
+ InetSocketAddress remote = (InetSocketAddress)clientChannel.remoteAddress();
+ addHeader("REMOTE_ADDR", remote.getAddress().getHostAddress());
+ addHeader("REMOTE_PORT", Integer.toString(remote.getPort()));
+
+ InetSocketAddress local = (InetSocketAddress)clientChannel.localAddress();
+ addHeader("SERVER_SOFTWARE", Responses.getServerHeaderValue());
+ addHeader("SERVER_NAME", Responses.getServerHeaderValue());
+
+ addHeader("SERVER_ADDR", local.getAddress().getHostAddress());
+ addHeader("SERVER_PORT", Integer.toString(local.getPort()));
+
+ addHeader("GATEWAY_INTERFACE", "CGI/1.1");
+ addHeader("SERVER_PROTOCOL", request.protocolVersion().text());
+ addHeader("CONTENT_TYPE", request.headers().get(HttpHeaders.Names.CONTENT_TYPE));
+
+ // PHP only, required if PHP was built with --enable-force-cgi-redirect
+ addHeader("REDIRECT_STATUS", "200");
+
+ String queryString = "";
+ int queryIndex = request.uri().indexOf('?');
+ if (queryIndex != -1) {
+ queryString = request.uri().substring(queryIndex + 1);
+ }
+ addHeader("QUERY_STRING", queryString);
+
+ addHeader("CONTENT_LENGTH", String.valueOf(request.content().readableBytes()));
+
+ for (Map.Entry<String, String> entry : request.headers()) {
+ addHeader("HTTP_" + entry.getKey().replace('-', '_').toUpperCase(Locale.ENGLISH), entry.getValue());
+ }
+ }
+
+ final void writeToServerChannel(ByteBuf content, Channel fastCgiChannel) {
+ writeHeader(buffer, PARAMS, 0);
+ fastCgiChannel.write(buffer);
+
+ if (content.isReadable()) {
+ ByteBuf headerBuffer = fastCgiChannel.alloc().buffer(FastCgiConstants.HEADER_LENGTH, FastCgiConstants.HEADER_LENGTH);
+ writeHeader(headerBuffer, STDIN, content.readableBytes());
+ fastCgiChannel.write(headerBuffer);
+
+ fastCgiChannel.write(content);
+
+ headerBuffer = fastCgiChannel.alloc().buffer(FastCgiConstants.HEADER_LENGTH, FastCgiConstants.HEADER_LENGTH);
+ writeHeader(headerBuffer, STDIN, 0);
+ fastCgiChannel.write(headerBuffer);
+ }
+ else {
+ content.release();
+ }
+
+ fastCgiChannel.flush();
+ }
+
+ private void writeHeader(ByteBuf buffer, int type, int length) {
+ buffer.writeByte(VERSION);
+ buffer.writeByte(type);
+ buffer.writeShort(requestId);
+ buffer.writeShort(length);
+ buffer.writeZero(2);
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java
new file mode 100644
index 000000000000..e249f7152c7c
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiResponse.java
@@ -0,0 +1,21 @@
+package org.jetbrains.io.fastCgi;
+
+import io.netty.buffer.ByteBuf;
+
+public class FastCgiResponse {
+ private final int id;
+ private final ByteBuf data;
+
+ public FastCgiResponse(int id, ByteBuf data) {
+ this.id = id;
+ this.data = data;
+ }
+
+ public ByteBuf getData() {
+ return data;
+ }
+
+ public int getId() {
+ return id;
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java
new file mode 100644
index 000000000000..54f13c6c8ea1
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/io/fastCgi/FastCgiService.java
@@ -0,0 +1,249 @@
+package org.jetbrains.io.fastCgi;
+
+import com.intellij.concurrency.JobScheduler;
+import com.intellij.execution.filters.TextConsoleBuilder;
+import com.intellij.execution.filters.TextConsoleBuilderFactory;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.ui.ConsoleView;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.AsyncResult;
+import com.intellij.openapi.util.AsyncValueLoader;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowAnchor;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.ui.content.ContentFactory;
+import com.intellij.util.Consumer;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap;
+import com.intellij.util.net.NetUtils;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.io.ChannelExceptionHandler;
+import org.jetbrains.io.NettyUtil;
+import org.jetbrains.io.Responses;
+
+import javax.swing.*;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+// todo send FCGI_ABORT_REQUEST if client channel disconnected
+public abstract class FastCgiService implements Disposable {
+ static final Logger LOG = Logger.getInstance(FastCgiService.class);
+
+ protected final Project project;
+
+ private final AtomicInteger requestIdCounter = new AtomicInteger();
+ private final StripedLockIntObjectConcurrentHashMap<Channel> requests = new StripedLockIntObjectConcurrentHashMap<Channel>();
+
+ private volatile Channel fastCgiChannel;
+
+ protected final AsyncValueLoader<OSProcessHandler> processHandler = new AsyncValueLoader<OSProcessHandler>() {
+ @Override
+ protected boolean isCancelOnReject() {
+ return true;
+ }
+
+ @Override
+ protected void load(@NotNull final AsyncResult<OSProcessHandler> result) throws IOException {
+ final int port = NetUtils.findAvailableSocketPort();
+ final OSProcessHandler processHandler = createProcessHandler(project, port);
+ if (processHandler == null) {
+ result.setRejected();
+ return;
+ }
+
+ result.doWhenRejected(new Runnable() {
+ @Override
+ public void run() {
+ processHandler.destroyProcess();
+ }
+ });
+
+ final MyProcessAdapter processListener = new MyProcessAdapter();
+ processHandler.addProcessListener(processListener);
+ processHandler.startNotify();
+
+ if (result.isRejected()) {
+ return;
+ }
+
+ JobScheduler.getScheduler().schedule(new Runnable() {
+ @Override
+ public void run() {
+ if (result.isRejected()) {
+ return;
+ }
+
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!result.isRejected()) {
+ try {
+ connectToProcess(result, port, processHandler, processListener);
+ }
+ catch (Throwable e) {
+ result.setRejected();
+ LOG.error(e);
+ }
+ }
+ }
+ });
+ }
+ }, NettyUtil.MIN_START_TIME, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ protected void disposeResult(@NotNull OSProcessHandler processHandler) {
+ try {
+ Channel currentFastCgiChannel = fastCgiChannel;
+ if (currentFastCgiChannel != null) {
+ fastCgiChannel = null;
+ NettyUtil.closeAndReleaseFactory(currentFastCgiChannel);
+ }
+ processHandler.destroyProcess();
+ }
+ finally {
+ requestIdCounter.set(0);
+ if (!requests.isEmpty()) {
+ List<Channel> waitingClients = ContainerUtil.toList(requests.elements());
+ requests.clear();
+ for (Channel channel : waitingClients) {
+ try {
+ if (channel.isActive()) {
+ Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel);
+ }
+ }
+ catch (Throwable e) {
+ NettyUtil.log(e, LOG);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ private ConsoleView console;
+
+ protected FastCgiService(Project project) {
+ this.project = project;
+ }
+
+ protected abstract OSProcessHandler createProcessHandler(Project project, int port);
+
+ private void connectToProcess(final AsyncResult<OSProcessHandler> asyncResult, final int port, final OSProcessHandler processHandler, final Consumer<String> errorOutputConsumer) {
+ Bootstrap bootstrap = NettyUtil.oioClientBootstrap();
+ final FastCgiChannelHandler fastCgiChannelHandler = new FastCgiChannelHandler(requests);
+ bootstrap.handler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ channel.pipeline().addLast(new FastCgiDecoder(errorOutputConsumer), fastCgiChannelHandler, ChannelExceptionHandler.getInstance());
+ }
+ });
+ fastCgiChannel = NettyUtil.connectClient(bootstrap, new InetSocketAddress(NetUtils.getLoopbackAddress(), port), asyncResult);
+ if (fastCgiChannel != null) {
+ asyncResult.setDone(processHandler);
+ }
+ }
+
+ public void send(final FastCgiRequest fastCgiRequest, final ByteBuf content) {
+ content.retain();
+
+ if (processHandler.has()) {
+ fastCgiRequest.writeToServerChannel(content, fastCgiChannel);
+ }
+ else {
+ processHandler.get().doWhenDone(new Runnable() {
+ @Override
+ public void run() {
+ fastCgiRequest.writeToServerChannel(content, fastCgiChannel);
+ }
+ }).doWhenRejected(new Runnable() {
+ @Override
+ public void run() {
+ content.release();
+ Channel channel = requests.get(fastCgiRequest.requestId);
+ if (channel != null && channel.isActive()) {
+ Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel);
+ }
+ }
+ });
+ }
+ }
+
+ public int allocateRequestId(Channel channel) {
+ int requestId = requestIdCounter.getAndIncrement();
+ if (requestId >= Short.MAX_VALUE) {
+ requestIdCounter.set(0);
+ requestId = requestIdCounter.getAndDecrement();
+ }
+ requests.put(requestId, channel);
+ return requestId;
+ }
+
+ @Override
+ public void dispose() {
+ processHandler.reset();
+ }
+
+ protected abstract void buildConsole(@NotNull TextConsoleBuilder consoleBuilder);
+
+ @NotNull
+ protected abstract String getConsoleToolWindowId();
+
+ @NotNull
+ protected abstract Icon getConsoleToolWindowIcon();
+
+ private final class MyProcessAdapter extends ProcessAdapter implements Consumer<String> {
+ private void createConsole() {
+ TextConsoleBuilder consoleBuilder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
+ buildConsole(consoleBuilder);
+ console = consoleBuilder.getConsole();
+
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ToolWindow toolWindow = ToolWindowManager.getInstance(project).registerToolWindow(getConsoleToolWindowId(), false, ToolWindowAnchor.BOTTOM, project, true);
+ toolWindow.setIcon(getConsoleToolWindowIcon());
+ toolWindow.getContentManager().addContent(ContentFactory.SERVICE.getInstance().createContent(console.getComponent(), "", false));
+ }
+ }, project.getDisposed());
+ }
+
+ @Override
+ public void onTextAvailable(ProcessEvent event, Key outputType) {
+ print(event.getText(), ConsoleViewContentType.getConsoleViewType(outputType));
+ }
+
+ private void print(String text, ConsoleViewContentType contentType) {
+ if (console == null) {
+ createConsole();
+ }
+ console.print(text, contentType);
+ }
+
+ @Override
+ public void processTerminated(ProcessEvent event) {
+ processHandler.reset();
+ print(getConsoleToolWindowId() + " terminated\n", ConsoleViewContentType.SYSTEM_OUTPUT);
+ }
+
+ @Override
+ public void consume(String message) {
+ print(message, ConsoleViewContentType.ERROR_OUTPUT);
+ }
+ }
+} \ No newline at end of file
diff --git a/xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java b/xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java
new file mode 100644
index 000000000000..4ea4a10dcf85
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/notification/SingletonNotificationManager.java
@@ -0,0 +1,86 @@
+package org.jetbrains.notification;
+
+import com.intellij.notification.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindowManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class SingletonNotificationManager {
+ private final AtomicReference<Notification> notification = new AtomicReference<Notification>();
+
+ private final NotificationGroup group;
+ private final NotificationType type;
+ @Nullable
+ private final NotificationListener listener;
+
+ private Runnable expiredListener;
+
+ public SingletonNotificationManager(@NotNull String groupId, @NotNull NotificationType type, @Nullable NotificationListener listener) {
+ this(new NotificationGroup(groupId, NotificationDisplayType.STICKY_BALLOON, true), type, listener);
+ }
+
+ public SingletonNotificationManager(@NotNull NotificationGroup group, @NotNull NotificationType type, @Nullable NotificationListener listener) {
+ this.group = group;
+ this.type = type;
+ this.listener = listener;
+ }
+
+ public boolean notify(@NotNull String title, @NotNull String content) {
+ return notify(title, content, null);
+ }
+
+ public boolean notify(@NotNull String title, @NotNull String content, @Nullable Project project) {
+ return notify(title, content, listener, project);
+ }
+
+ public boolean notify(@NotNull String content, @Nullable Project project) {
+ return notify("", content, listener, project);
+ }
+
+ public boolean notify(@NotNull String title,
+ @NotNull String content,
+ @Nullable NotificationListener listener,
+ @Nullable Project project) {
+ Notification oldNotification = notification.get();
+ // !oldNotification.isExpired() is not enough - notification could be closed, but not expired
+ if (oldNotification != null) {
+ if (!oldNotification.isExpired() && (oldNotification.getBalloon() != null ||
+ (project != null &&
+ group.getDisplayType() == NotificationDisplayType.TOOL_WINDOW &&
+ ToolWindowManager.getInstance(project).getToolWindowBalloon(group.getToolWindowId()) != null))) {
+ return false;
+ }
+ oldNotification.whenExpired(null);
+ oldNotification.expire();
+ }
+
+ if (expiredListener == null) {
+ expiredListener = new Runnable() {
+ @Override
+ public void run() {
+ Notification currentNotification = notification.get();
+ if (currentNotification != null && currentNotification.isExpired()) {
+ notification.compareAndSet(currentNotification, null);
+ }
+ }
+ };
+ }
+
+ Notification newNotification = group.createNotification(title, content, type, listener);
+ newNotification.whenExpired(expiredListener);
+ notification.set(newNotification);
+ newNotification.notify(project);
+ return true;
+ }
+
+ public void clear() {
+ Notification oldNotification = notification.getAndSet(null);
+ if (oldNotification != null) {
+ oldNotification.whenExpired(null);
+ oldNotification.expire();
+ }
+ }
+} \ No newline at end of file
diff --git a/xml/impl/xml.iml b/xml/impl/xml.iml
index 60e210829091..2fb3ee7ea262 100644
--- a/xml/impl/xml.iml
+++ b/xml/impl/xml.iml
@@ -21,6 +21,8 @@
<orderEntry type="module" module-name="xml-analysis-impl" exported="" />
<orderEntry type="library" name="swingx" level="project" />
<orderEntry type="module" module-name="xml-structure-view-impl" exported="" />
+ <orderEntry type="library" name="Netty" level="project" />
+ <orderEntry type="module" module-name="xdebugger-api" />
</component>
<component name="copyright">
<Base>