diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/BaseExternalSystemProgressEvent.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/BaseExternalSystemProgressEvent.java
new file mode 100644
index 000000000000..507b99ea5f41
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/BaseExternalSystemProgressEvent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public class BaseExternalSystemProgressEvent implements ExternalSystemProgressEvent {
+ @NotNull private final String myEventId;
+ @NotNull private final String myDescription;
+ @Nullable private final String myParentEventId;
+ private final long myEventTime;
+
+ public BaseExternalSystemProgressEvent(@NotNull String eventId,
+ @Nullable String parentEventId,
+ @NotNull String description,
+ long eventTime) {
+ myEventId = eventId;
+ myDescription = description;
+ myParentEventId = parentEventId;
+ myEventTime = eventTime;
+ }
+
+ @NotNull
+ @Override
+ public String getEventId() {
+ return myEventId;
+ }
+
+ @Nullable
+ @Override
+ public String getParentEventId() {
+ return myParentEventId;
+ }
+
+ @NotNull
+ @Override
+ public String getDescription() {
+ return myDescription;
+ }
+
+ @Override
+ public long getEventTime() {
+ return myEventTime;
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/DefaultOperationResult.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/DefaultOperationResult.java
new file mode 100644
index 000000000000..b2c4fdd7a7ed
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/DefaultOperationResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/2/2015
+ */
+public class DefaultOperationResult implements OperationResult {
+
+ private final long myStartTime;
+ private final long myEndTime;
+
+ public DefaultOperationResult(long startTime, long endTime) {
+ myStartTime = startTime;
+ myEndTime = endTime;
+ }
+
+ @Override
+ public long getStartTime() {
+ return myStartTime;
+ }
+
+ @Override
+ public long getEndTime() {
+ return myEndTime;
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemFinishEvent.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemFinishEvent.java
new file mode 100644
index 000000000000..1b4e609edef4
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemFinishEvent.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public interface ExternalSystemFinishEvent extends ExternalSystemProgressEvent {
+ OperationResult getOperationResult();
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemFinishEventImpl.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemFinishEventImpl.java
new file mode 100644
index 000000000000..7998d3a820b1
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemFinishEventImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public class ExternalSystemFinishEventImpl extends BaseExternalSystemProgressEvent implements ExternalSystemFinishEvent {
+ @NotNull
+ private final OperationResult myResult;
+ public ExternalSystemFinishEventImpl(@NotNull String eventId,
+ @Nullable String parentEventId,
+ @NotNull String description,
+ @NotNull OperationResult result) {
+ super(eventId, parentEventId, description, result.getEndTime());
+ myResult = result;
+ }
+
+ @Override
+ public OperationResult getOperationResult() {
+ return myResult;
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemProgressEvent.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemProgressEvent.java
new file mode 100644
index 000000000000..7a7af09271ed
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemProgressEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.Serializable;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public interface ExternalSystemProgressEvent extends Serializable {
+ @NotNull
+ String getEventId();
+
+ @Nullable
+ String getParentEventId();
+
+ @NotNull
+ String getDescription();
+
+ long getEventTime();
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemStartEvent.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemStartEvent.java
new file mode 100644
index 000000000000..be34037262f8
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemStartEvent.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public interface ExternalSystemStartEvent extends ExternalSystemProgressEvent{
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemStartEventImpl.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemStartEventImpl.java
new file mode 100644
index 000000000000..c8ac0b1aa164
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemStartEventImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public class ExternalSystemStartEventImpl extends BaseExternalSystemProgressEvent implements ExternalSystemStartEvent {
+ public ExternalSystemStartEventImpl(@NotNull String eventId, @Nullable String parentEventId, @NotNull String description, long eventTime) {
+ super(eventId, parentEventId, description, eventTime);
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemTaskExecutionEvent.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemTaskExecutionEvent.java
new file mode 100644
index 000000000000..e3ffb0b9d35b
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/ExternalSystemTaskExecutionEvent.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public class ExternalSystemTaskExecutionEvent extends ExternalSystemTaskNotificationEvent {
+
+ private static final long serialVersionUID = 1L;
+ @NotNull private final ExternalSystemProgressEvent myProgressEvent;
+
+ public ExternalSystemTaskExecutionEvent(@NotNull ExternalSystemTaskId id, @NotNull ExternalSystemProgressEvent progressEvent) {
+ super(id, progressEvent.getDescription());
+ myProgressEvent = progressEvent;
+ }
+
+ @NotNull
+ public ExternalSystemProgressEvent getProgressEvent() {
+ return myProgressEvent;
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/FailureResult.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/FailureResult.java
new file mode 100644
index 000000000000..826696a244e0
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/FailureResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public interface FailureResult extends OperationResult {
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/FailureResultImpl.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/FailureResultImpl.java
new file mode 100644
index 000000000000..4d90fcbf4bff
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/FailureResultImpl.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/2/2015
+ */
+public class FailureResultImpl extends DefaultOperationResult implements FailureResult{
+ public FailureResultImpl(long startTime, long endTime) {
+ super(startTime, endTime);
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/OperationResult.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/OperationResult.java
new file mode 100644
index 000000000000..9cf5d94a32ee
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/OperationResult.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+import java.io.Serializable;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public interface OperationResult extends Serializable {
+ long getStartTime();
+ long getEndTime();
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SkippedResult.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SkippedResult.java
new file mode 100644
index 000000000000..e1547a2b002f
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SkippedResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public interface SkippedResult extends OperationResult {
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SkippedResultImpl.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SkippedResultImpl.java
new file mode 100644
index 000000000000..a21ababa320a
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SkippedResultImpl.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/2/2015
+ */
+public class SkippedResultImpl extends DefaultOperationResult implements SkippedResult {
+ public SkippedResultImpl(long startTime, long endTime) {
+ super(startTime, endTime);
+ }
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SuccessResult.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SuccessResult.java
new file mode 100644
index 000000000000..1a0d2a14e194
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SuccessResult.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public interface SuccessResult extends OperationResult {
+ boolean isUpToDate();
+}
diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SuccessResultImpl.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SuccessResultImpl.java
new file mode 100644
index 000000000000..fd6704fd2492
--- /dev/null
+++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/event/SuccessResultImpl.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2015 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.openapi.externalSystem.model.task.event;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/2/2015
+ */
+public class SuccessResultImpl extends DefaultOperationResult implements SuccessResult {
+
+ private final boolean myUpToDate;
+
+ public SuccessResultImpl(long startTime, long endTime, boolean isUpToDate) {
+ super(startTime, endTime);
+ myUpToDate = isUpToDate;
+ }
+
+ @Override
+ public boolean isUpToDate() {
+ return myUpToDate;
+ }
+}
\ No newline at end of file
diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties
index dc149f88a0b1..ac3ed8153a32 100644
--- a/plugins/gradle/resources/i18n/GradleBundle.properties
+++ b/plugins/gradle/resources/i18n/GradleBundle.properties
@@ -37,6 +37,7 @@ gradle.codeInsight.action.add_maven_dependency.text=Add maven artifact dependenc
gradle.codeInsight.action.add_maven_dependency.description=Add selected maven artifact dependency to the project
gradle.runner=Runner
+gradle.runner.toggle.tree.text.action.name=Toggle tasks executions/text mode
gradle.preferred_test_runner.ask=Let me choose per test
gradle.preferred_test_runner.PLATFORM_TEST_RUNNER=Platform Test Runner
gradle.preferred_test_runner.GRADLE_TEST_RUNNER=Gradle Test Runner
diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml
index 121810970cf6..c9726666c5d6 100644
--- a/plugins/gradle/src/META-INF/plugin.xml
+++ b/plugins/gradle/src/META-INF/plugin.xml
@@ -91,6 +91,7 @@
order="last"/>
+
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/ExecutionInfo.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/ExecutionInfo.java
new file mode 100644
index 000000000000..77f05ff2d4bb
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/ExecutionInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2000-2015 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.plugins.gradle.execution;
+
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public class ExecutionInfo {
+ private final @Nullable String id;
+ private String myDisplayName;
+ private long startTime;
+ private long endTime;
+ private boolean isFailed;
+ private boolean isSkipped;
+ private boolean isUpToDate;
+
+ public ExecutionInfo(@Nullable String id, String displayName) {
+ this.id = id;
+ this.myDisplayName = displayName;
+ }
+
+ @Nullable
+ public String getId() {
+ return id;
+ }
+
+ public String getDisplayName() {
+ return myDisplayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.myDisplayName = displayName;
+ }
+
+ public long getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(long startTime) {
+ this.startTime = startTime;
+ }
+
+ public long getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(long endTime) {
+ this.endTime = endTime;
+ }
+
+ public boolean isFailed() {
+ return isFailed;
+ }
+
+ public void setFailed(boolean failed) {
+ isFailed = failed;
+ }
+
+ public boolean isSkipped() {
+ return isSkipped;
+ }
+
+ public void setSkipped(boolean skipped) {
+ isSkipped = skipped;
+ }
+
+ public boolean isUpToDate() {
+ return isUpToDate;
+ }
+
+ public void setUpToDate(boolean upToDate) {
+ isUpToDate = upToDate;
+ }
+
+ public boolean isRunning() {
+ return endTime <= 0;
+ }
+
+ @Override
+ public String toString() {
+ return myDisplayName;
+ }
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/ExecutionNode.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/ExecutionNode.java
new file mode 100644
index 000000000000..494e3a5bdb60
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/ExecutionNode.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2015 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.plugins.gradle.execution;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.treeStructure.CachingSimpleNode;
+import com.intellij.ui.treeStructure.SimpleTree;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public class ExecutionNode extends CachingSimpleNode {
+
+ @NotNull
+ private ExecutionInfo myInfo;
+ private final List myNodes = ContainerUtil.newArrayList();
+
+ protected ExecutionNode(Project project) {
+ super(project, null);
+ this.myInfo = new ExecutionInfo(null, "--");
+ }
+
+ public void add(ExecutionNode node) {
+ myNodes.add(node);
+ cleanUpCache();
+ }
+
+ @NotNull
+ public ExecutionInfo getInfo() {
+ return myInfo;
+ }
+
+ public void setInfo(@NotNull ExecutionInfo info) {
+ myInfo = info;
+ }
+
+ @Override
+ public String getName() {
+ return myInfo.getDisplayName();
+ }
+
+ public String getDuration() {
+ if (myInfo.isRunning()) {
+ final long duration = myInfo.getStartTime() == 0 ? 0 : System.currentTimeMillis() - myInfo.getStartTime();
+ return "Running for " + StringUtil.formatDuration(duration);
+ }
+ else {
+ return StringUtil.formatDuration(myInfo.getEndTime() - myInfo.getStartTime());
+ }
+ }
+
+ @Override
+ public boolean isAutoExpandNode() {
+ return true;
+ }
+
+ @Override
+ protected ExecutionNode[] buildChildren() {
+ return ContainerUtil.toArray(myNodes, new ExecutionNode[myNodes.size()]);
+ }
+
+ @Override
+ protected void doUpdate() {
+ setNameAndTooltip(getName(), null, myInfo.isUpToDate() ? "UP-TO-DATE" : null);
+ setIcon(
+ myInfo.isRunning() ? NodeProgressAnimator.getCurrentFrame() :
+ myInfo.isFailed() ? AllIcons.RunConfigurations.TestError :
+ myInfo.isSkipped() ? AllIcons.RunConfigurations.TestError :
+ AllIcons.RunConfigurations.TestPassed
+ );
+ }
+
+ @Override
+ public void handleSelection(SimpleTree tree) {
+ super.handleSelection(tree);
+ }
+
+ protected void setNameAndTooltip(String name, @Nullable String tooltip) {
+ setNameAndTooltip(name, tooltip, (String)null);
+ }
+
+ protected void setNameAndTooltip(String name, @Nullable String tooltip, @Nullable String hint) {
+ final SimpleTextAttributes textAttributes = getPlainAttributes();
+ setNameAndTooltip(name, tooltip, textAttributes);
+ if (!StringUtil.isEmptyOrSpaces(hint)) {
+ addColoredFragment(" " + hint, SimpleTextAttributes.GRAY_ATTRIBUTES);
+ }
+ }
+
+ protected void setNameAndTooltip(String name, @Nullable String tooltip, SimpleTextAttributes attributes) {
+ clearColoredText();
+ addColoredFragment(name, prepareAttributes(attributes));
+ final String s = (tooltip != null ? tooltip + "\n\r" : "");
+ getTemplatePresentation().setTooltip(s);
+ }
+
+ private static SimpleTextAttributes prepareAttributes(SimpleTextAttributes from) {
+ return new SimpleTextAttributes(from.getBgColor(), from.getFgColor(), null, from.getStyle());
+ }
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleExecutionConsoleManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleExecutionConsoleManager.java
new file mode 100644
index 000000000000..eaadb450a110
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleExecutionConsoleManager.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2000-2015 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.plugins.gradle.execution;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.filters.TextConsoleBuilderFactory;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.ui.ConsoleView;
+import com.intellij.execution.ui.ExecutionConsole;
+import com.intellij.openapi.externalSystem.model.ProjectSystemId;
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask;
+import com.intellij.openapi.externalSystem.service.execution.DefaultExternalSystemExecutionConsoleManager;
+import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.gradle.util.GradleConstants;
+
+import static org.jetbrains.plugins.gradle.execution.GradleRunnerUtil.attachTaskExecutionView;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public class GradleExecutionConsoleManager extends DefaultExternalSystemExecutionConsoleManager {
+
+ @NotNull
+ @Override
+ public ProjectSystemId getExternalSystemId() {
+ return GradleConstants.SYSTEM_ID;
+ }
+
+ @NotNull
+ @Override
+ public ExecutionConsole attachExecutionConsole(@NotNull ExternalSystemTask task,
+ @NotNull Project project,
+ @NotNull ExternalSystemRunConfiguration configuration,
+ @NotNull Executor executor,
+ @NotNull ExecutionEnvironment env,
+ @NotNull ProcessHandler processHandler) throws ExecutionException {
+ final ConsoleView textConsole = TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole();
+ textConsole.attachToProcess(processHandler);
+ return attachTaskExecutionView(project, textConsole, true, "gradle.runner.text.console", processHandler, task.getId());
+ }
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunnerUtil.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunnerUtil.java
new file mode 100644
index 000000000000..5801bf504519
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunnerUtil.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2015 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.plugins.gradle.execution;
+
+import com.intellij.execution.console.DuplexConsoleView;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.ui.ConsoleView;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent;
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
+import com.intellij.openapi.externalSystem.model.task.event.ExternalSystemTaskExecutionEvent;
+import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.gradle.util.GradleBundle;
+
+import java.util.List;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/4/2015
+ */
+public class GradleRunnerUtil {
+
+ public static DuplexConsoleView attachTaskExecutionView(@NotNull final Project project,
+ @NotNull final ConsoleView consoleView,
+ final boolean isTaskConsoleEnabledByDefault,
+ @Nullable final String stateStorageKey,
+ @NotNull final ProcessHandler processHandler,
+ @NotNull final ExternalSystemTaskId taskId) {
+ if (stateStorageKey != null && isTaskConsoleEnabledByDefault && !PropertiesComponent.getInstance().isValueSet(stateStorageKey)) {
+ PropertiesComponent.getInstance().setValue(stateStorageKey, true);
+ }
+ ;
+
+ final TaskExecutionView gradleExecutionConsole = new TaskExecutionView(project);
+ final Ref duplexConsoleViewRef = Ref.create();
+ final DuplexConsoleView duplexConsoleView =
+ new DuplexConsoleView(gradleExecutionConsole, consoleView, stateStorageKey) {
+ @NotNull
+ @Override
+ public AnAction[] createConsoleActions() {
+
+ final DefaultActionGroup textActionGroup = new DefaultActionGroup() {
+ @Override
+ public void update(AnActionEvent e) {
+ super.update(e);
+ if (duplexConsoleViewRef.get() != null) {
+ e.getPresentation().setVisible(!duplexConsoleViewRef.get().isPrimaryConsoleEnabled());
+ }
+ }
+ };
+ final AnAction[] consoleActions = consoleView.createConsoleActions();
+ for (AnAction anAction : consoleActions) {
+ textActionGroup.add(anAction);
+ }
+
+ final List anActions = ContainerUtil.newArrayList(super.createConsoleActions());
+ anActions.add(textActionGroup);
+ return ArrayUtil.toObjectArray(anActions, AnAction.class);
+ }
+ };
+
+ duplexConsoleViewRef.set(duplexConsoleView);
+
+ duplexConsoleView.setDisableSwitchConsoleActionOnProcessEnd(false);
+ duplexConsoleView.getSwitchConsoleActionPresentation().setIcon(AllIcons.Debugger.Console);
+ duplexConsoleView.getSwitchConsoleActionPresentation().setText(GradleBundle.message("gradle.runner.toggle.tree.text.action.name"));
+
+ final ExternalSystemProgressNotificationManager progressManager =
+ ServiceManager.getService(ExternalSystemProgressNotificationManager.class);
+ final ExternalSystemTaskNotificationListenerAdapter taskListener = new ExternalSystemTaskNotificationListenerAdapter() {
+ @Override
+ public void onStatusChange(@NotNull final ExternalSystemTaskNotificationEvent event) {
+ if (event instanceof ExternalSystemTaskExecutionEvent) {
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ gradleExecutionConsole.onStatusChange((ExternalSystemTaskExecutionEvent)event);
+ }
+ });
+ }
+ }
+ };
+ progressManager.addNotificationListener(taskId, taskListener);
+
+ processHandler.addProcessListener(new ProcessAdapter() {
+ @Override
+ public void processTerminated(ProcessEvent event) {
+ progressManager.removeNotificationListener(taskListener);
+ }
+ });
+
+ return duplexConsoleView;
+ }
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/NodeProgressAnimator.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/NodeProgressAnimator.java
new file mode 100644
index 000000000000..b3868675eff1
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/NodeProgressAnimator.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2000-2015 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.plugins.gradle.execution;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.util.treeView.AbstractTreeBuilder;
+import com.intellij.ide.util.treeView.AbstractTreeUi;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.ui.treeStructure.SimpleNode;
+import com.intellij.util.Alarm;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 11/27/2015
+ */
+public class NodeProgressAnimator implements Runnable, Disposable {
+ private static final int FRAMES_COUNT = 8;
+ private static final int MOVIE_TIME = 800;
+ private static final int FRAME_TIME = MOVIE_TIME / FRAMES_COUNT;
+
+ public static final Icon[] FRAMES = new Icon[FRAMES_COUNT];
+
+ private long myLastInvocationTime = -1;
+
+ private Alarm myAlarm;
+ private SimpleNode myCurrentNode;
+ private AbstractTreeBuilder myTreeBuilder;
+
+ public NodeProgressAnimator(AbstractTreeBuilder builder) {
+ Disposer.register(builder, this);
+ init(builder);
+ }
+
+ static {
+ FRAMES[0] = AllIcons.RunConfigurations.TestInProgress1;
+ FRAMES[1] = AllIcons.RunConfigurations.TestInProgress2;
+ FRAMES[2] = AllIcons.RunConfigurations.TestInProgress3;
+ FRAMES[3] = AllIcons.RunConfigurations.TestInProgress4;
+ FRAMES[4] = AllIcons.RunConfigurations.TestInProgress5;
+ FRAMES[5] = AllIcons.RunConfigurations.TestInProgress6;
+ FRAMES[6] = AllIcons.RunConfigurations.TestInProgress7;
+ FRAMES[7] = AllIcons.RunConfigurations.TestInProgress8;
+ }
+
+ public static int getCurrentFrameIndex() {
+ return (int) ((System.currentTimeMillis() % MOVIE_TIME) / FRAME_TIME);
+ }
+
+ public static Icon getCurrentFrame() {
+ return FRAMES[getCurrentFrameIndex()];
+ }
+
+ /**
+ * Initializes animator: creates alarm and sets tree builder
+ * @param treeBuilder tree builder
+ */
+ protected void init(final AbstractTreeBuilder treeBuilder) {
+ myAlarm = new Alarm();
+ myTreeBuilder = treeBuilder;
+ }
+
+ public SimpleNode getCurrentNode() {
+ return myCurrentNode;
+ }
+
+ public void run() {
+ if (myCurrentNode != null) {
+ final long time = System.currentTimeMillis();
+ // optimization:
+ // we shouldn't repaint if this frame was painted in current interval
+ if (time - myLastInvocationTime >= FRAME_TIME) {
+ repaintSubTree();
+ myLastInvocationTime = time;
+ }
+ }
+ scheduleRepaint();
+ }
+
+ public void setCurrentNode(@Nullable final SimpleNode node) {
+ myCurrentNode = node;
+ scheduleRepaint();
+ }
+
+ public void stopMovie() {
+ repaintSubTree();
+ setCurrentNode(null);
+ cancelAlarm();
+ }
+
+
+ public void dispose() {
+ myTreeBuilder = null;
+ myCurrentNode = null;
+ cancelAlarm();
+ }
+
+ private void cancelAlarm() {
+ if (myAlarm != null) {
+ myAlarm.cancelAllRequests();
+ myAlarm = null;
+ }
+ }
+
+ private void repaintSubTree() {
+ if (myTreeBuilder != null && myCurrentNode != null) {
+ repaintWithParents(myCurrentNode);
+ }
+ }
+
+ public void repaintWithParents(final SimpleNode element) {
+ SimpleNode current = element;
+ do {
+ DefaultMutableTreeNode node = myTreeBuilder.getNodeForElement(current);
+ if (node != null) {
+ final AbstractTreeUi treeUi = myTreeBuilder.getUi();
+ treeUi.addSubtreeToUpdate(node, false);
+ }
+ current = current.getParent();
+ }
+ while (current != null);
+ }
+
+
+ private void scheduleRepaint() {
+ if (myAlarm == null) {
+ return;
+ }
+ myAlarm.cancelAllRequests();
+ if (myCurrentNode != null) {
+ myAlarm.addRequest(this, FRAME_TIME);
+ }
+ }
+
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/TaskExecutionView.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/TaskExecutionView.java
new file mode 100644
index 000000000000..3bf5a33411c0
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/TaskExecutionView.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2000-2015 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.plugins.gradle.execution;
+
+import com.intellij.execution.filters.Filter;
+import com.intellij.execution.filters.HyperlinkInfo;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.ui.ConsoleView;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.externalSystem.model.task.event.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.ui.*;
+import com.intellij.ui.speedSearch.SpeedSearchUtil;
+import com.intellij.ui.treeStructure.SimpleTreeBuilder;
+import com.intellij.ui.treeStructure.SimpleTreeStructure;
+import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns;
+import com.intellij.ui.treeStructure.treetable.TreeColumnInfo;
+import com.intellij.ui.treeStructure.treetable.TreeTable;
+import com.intellij.ui.treeStructure.treetable.TreeTableTree;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.ColumnInfo;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeCellRenderer;
+import java.awt.*;
+import java.util.Map;
+
+/**
+ * @author Vladislav.Soroka
+ * @since 12/1/2015
+ */
+public class TaskExecutionView implements ConsoleView {
+
+ private static final int TIME_COLUMN_WIDTH = 140;
+ private final Project myProject;
+ private final Map nodeMap = ContainerUtil.newHashMap();
+ private final JScrollPane myPane;
+ private final TreeTable myTreeTable;
+ private final SimpleTreeBuilder myBuilder;
+ private final NodeProgressAnimator myProgressAnimator;
+ private final ExecutionNode myRoot;
+
+ public TaskExecutionView(Project project) {
+ myProject = project;
+ final ColumnInfo[] COLUMNS = new ColumnInfo[]{
+ new TreeColumnInfo("name"),
+ new ColumnInfo("time elapsed") {
+ @Nullable
+ @Override
+ public Object valueOf(Object o) {
+ if (o instanceof DefaultMutableTreeNode) {
+ final Object userObject = ((DefaultMutableTreeNode)o).getUserObject();
+ if (userObject instanceof ExecutionNode) {
+ return ((ExecutionNode)userObject).getDuration();
+ }
+ }
+ return null;
+ }
+ }
+ ,
+ new ColumnInfo("") {
+ @Nullable
+ @Override
+ public Object valueOf(Object o) {
+ return "";
+ }
+ }
+ };
+ myRoot = new ExecutionNode(project);
+ myRoot.setInfo(new ExecutionInfo(null, "Run build"));
+ final ListTreeTableModelOnColumns model = new ListTreeTableModelOnColumns(new DefaultMutableTreeNode(myRoot), COLUMNS);
+
+ myTreeTable = new TreeTable(model) {
+ @Override
+ public TableCellRenderer getCellRenderer(int row, int column) {
+ if (column == 1) {
+ return new DefaultTableCellRenderer() {
+ @Override
+ public Component getTableCellRendererComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ int row,
+ int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ setHorizontalAlignment(SwingConstants.RIGHT);
+ return this;
+ }
+ };
+ }
+ return super.getCellRenderer(row, column);
+ }
+ };
+
+ final TreeCellRenderer treeCellRenderer = myTreeTable.getTree().getCellRenderer();
+
+ myTreeTable.getTree().setCellRenderer(new TreeCellRenderer() {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value,
+ boolean selected,
+ boolean expanded,
+ boolean leaf,
+ int row,
+ boolean hasFocus) {
+ final Component rendererComponent =
+ treeCellRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+ if (rendererComponent instanceof SimpleColoredComponent) {
+ final Color bg = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground();
+ final Color fg = selected ? UIUtil.getTreeSelectionForeground() : UIUtil.getTreeForeground();
+ if (selected) {
+ for (SimpleColoredComponent.ColoredIterator it = ((SimpleColoredComponent)rendererComponent).iterator(); it.hasNext(); ) {
+ it.next();
+ int offset = it.getOffset();
+ int endOffset = it.getEndOffset();
+ SimpleTextAttributes currentAttributes = it.getTextAttributes();
+ SimpleTextAttributes newAttributes =
+ new SimpleTextAttributes(bg, fg, currentAttributes.getWaveColor(), currentAttributes.getStyle());
+ it.split(endOffset - offset, newAttributes);
+ }
+ }
+
+ SpeedSearchUtil.applySpeedSearchHighlighting(myTreeTable, (SimpleColoredComponent)rendererComponent, true, selected);
+ }
+ return rendererComponent;
+ }
+ });
+
+ new TreeTableSpeedSearch(myTreeTable).setComparator(new SpeedSearchComparator(false));
+ myTreeTable.setTableHeader(null);
+
+ final TableColumn treeColumn = myTreeTable.getColumnModel().getColumn(0);
+ treeColumn.setMinWidth(300);
+ final TableColumn timeColumn = myTreeTable.getColumnModel().getColumn(1);
+ timeColumn.setMaxWidth(TIME_COLUMN_WIDTH);
+ timeColumn.setMinWidth(TIME_COLUMN_WIDTH);
+
+ TreeTableTree tree = myTreeTable.getTree();
+ final SimpleTreeStructure treeStructure = new SimpleTreeStructure.Impl(myRoot);
+
+ myBuilder = new SimpleTreeBuilder(tree, model, treeStructure, null);
+ Disposer.register(this, myBuilder);
+ myBuilder.expand(treeStructure.getRootElement(), null);
+
+ myBuilder.initRoot();
+ myBuilder.expand(myRoot, null);
+ myProgressAnimator = new NodeProgressAnimator(myBuilder);
+ myProgressAnimator.setCurrentNode(myRoot);
+ myBuilder.queueUpdateFrom(myRoot, false, true);
+
+ myPane = ScrollPaneFactory.createScrollPane(myTreeTable,
+ ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+ ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ }
+
+ public void onStatusChange(ExternalSystemTaskExecutionEvent event) {
+ final ExternalSystemProgressEvent progressEvent = event.getProgressEvent();
+ final String parentEventId = progressEvent.getParentEventId();
+ if (progressEvent instanceof ExternalSystemStartEvent) {
+ final ExecutionInfo executionInfo = new ExecutionInfo(progressEvent.getEventId(), progressEvent.getDescription());
+ executionInfo.setStartTime(progressEvent.getEventTime());
+ final ExecutionNode currentNode = parentEventId == null ? myRoot : new ExecutionNode(myProject);
+ if (parentEventId != null) {
+ final ExecutionNode parentNode = nodeMap.get(parentEventId);
+ if (parentNode != null) {
+ parentNode.add(currentNode);
+ }
+ }
+ currentNode.setInfo(executionInfo);
+ nodeMap.put(progressEvent.getEventId(), currentNode);
+
+ myProgressAnimator.setCurrentNode(currentNode);
+ myBuilder.queueUpdateFrom(currentNode, false, true);
+ }
+ else if (progressEvent instanceof ExternalSystemFinishEvent) {
+ final ExecutionInfo executionInfo;
+ final ExecutionNode node = nodeMap.get(progressEvent.getEventId());
+ executionInfo = node.getInfo();
+ executionInfo.setDisplayName(progressEvent.getDescription());
+ executionInfo.setEndTime(progressEvent.getEventTime());
+ final OperationResult operationResult = ((ExternalSystemFinishEvent)progressEvent).getOperationResult();
+ if (operationResult instanceof FailureResult) {
+ executionInfo.setFailed(true);
+ }
+ else if (operationResult instanceof SkippedResult) {
+ executionInfo.setSkipped(true);
+ }
+ else if (operationResult instanceof SuccessResult) {
+ executionInfo.setUpToDate(((SuccessResult)operationResult).isUpToDate());
+ }
+ if (parentEventId == null) {
+ myProgressAnimator.stopMovie();
+ }
+
+ myBuilder.queueUpdateFrom(node, false, false);
+ }
+ }
+
+ @Override
+ public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) {
+ }
+
+ @Override
+ public void clear() {
+
+ }
+
+ @Override
+ public void scrollTo(int offset) {
+
+ }
+
+ @Override
+ public void attachToProcess(ProcessHandler processHandler) {
+
+ }
+
+ @Override
+ public void setOutputPaused(boolean value) {
+
+ }
+
+ @Override
+ public boolean isOutputPaused() {
+ return false;
+ }
+
+ @Override
+ public boolean hasDeferredOutput() {
+ return false;
+ }
+
+ @Override
+ public void performWhenNoDeferredOutput(Runnable runnable) {
+
+ }
+
+ @Override
+ public void setHelpId(String helpId) {
+
+ }
+
+ @Override
+ public void addMessageFilter(Filter filter) {
+
+ }
+
+ @Override
+ public void printHyperlink(String hyperlinkText, HyperlinkInfo info) {
+
+ }
+
+ @Override
+ public int getContentSize() {
+ return 0;
+ }
+
+ @Override
+ public boolean canPause() {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public AnAction[] createConsoleActions() {
+ return new AnAction[0];
+ }
+
+ @Override
+ public void allowHeavyFilters() {
+
+ }
+
+ @Override
+ public JComponent getComponent() {
+ return myPane;
+ }
+
+ @Override
+ public JComponent getPreferredFocusableComponent() {
+ return myTreeTable;
+ }
+
+ @Override
+ public void dispose() {
+ }
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsole.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsole.java
index 04b734f2f6f8..29ae04e74bcb 100644
--- a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsole.java
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsole.java
@@ -15,35 +15,45 @@
*/
package org.jetbrains.plugins.gradle.execution.test.runner;
-import com.intellij.execution.filters.Filter;
-import com.intellij.execution.filters.HyperlinkInfo;
+import com.intellij.execution.console.DuplexConsoleView;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.sm.runner.SMTestLocator;
import com.intellij.execution.testframework.sm.runner.SMTestProxy;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
-import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
import com.intellij.execution.ui.ConsoleView;
-import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
+import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
-import javax.swing.*;
import java.util.Map;
+import static org.jetbrains.plugins.gradle.execution.GradleRunnerUtil.attachTaskExecutionView;
+
/**
* @author Vladislav.Soroka
* @since 10/28/2015
*/
-public class GradleTestsExecutionConsole implements ConsoleView {
+public class GradleTestsExecutionConsole extends SMTRunnerConsoleView {
private Map testsMap = ContainerUtil.newHashMap();
private StringBuilder myBuffer = new StringBuilder();
- private SMTRunnerConsoleView myExecutionConsole;
+ private DuplexConsoleView myConsoleView;
- public GradleTestsExecutionConsole(SMTRunnerConsoleView executionConsole) {
- myExecutionConsole = executionConsole;
+ public GradleTestsExecutionConsole(TestConsoleProperties consoleProperties, @Nullable String splitterProperty) {
+ super(consoleProperties, splitterProperty);
+ }
+
+ public void initTaskExecutionView(Project project, ProcessHandler processHandler, ExternalSystemTaskId taskId) {
+ myConsoleView = attachTaskExecutionView(project, super.getConsole(), false, "gradle.test.runner.text.console", processHandler, taskId);
+ }
+
+ @NotNull
+ @Override
+ public ConsoleView getConsole() {
+ return myConsoleView != null ? myConsoleView : super.getConsole();
}
public Map getTestsMap() {
@@ -54,103 +64,10 @@ public class GradleTestsExecutionConsole implements ConsoleView {
return myBuffer;
}
- @Override
- public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) {
- myExecutionConsole.print(s, contentType);
- }
-
- @Override
- public void clear() {
- myExecutionConsole.clear();
- }
-
- @Override
- public void scrollTo(int offset) {
- myExecutionConsole.scrollTo(offset);
- }
-
- @Override
- public void attachToProcess(ProcessHandler processHandler) {
- myExecutionConsole.attachToProcess(processHandler);
- }
-
- @Override
- public void setOutputPaused(boolean value) {
- myExecutionConsole.setOutputPaused(value);
- }
-
- @Override
- public boolean isOutputPaused() {
- return myExecutionConsole.isOutputPaused();
- }
-
- @Override
- public boolean hasDeferredOutput() {
- return myExecutionConsole.hasDeferredOutput();
- }
-
- @Override
- public void performWhenNoDeferredOutput(Runnable runnable) {
- myExecutionConsole.performWhenNoDeferredOutput(runnable);
- }
-
- @Override
- public void setHelpId(String helpId) {
- myExecutionConsole.setHelpId(helpId);
- }
-
- @Override
- public void addMessageFilter(Filter filter) {
- myExecutionConsole.addMessageFilter(filter);
- }
-
- @Override
- public void printHyperlink(String hyperlinkText, HyperlinkInfo info) {
- myExecutionConsole.printHyperlink(hyperlinkText, info);
- }
-
- @Override
- public int getContentSize() {
- return myExecutionConsole.getContentSize();
- }
-
- @Override
- public boolean canPause() {
- return myExecutionConsole.canPause();
- }
-
- @NotNull
- @Override
- public AnAction[] createConsoleActions() {
- return myExecutionConsole.createConsoleActions();
- }
-
- @Override
- public void allowHeavyFilters() {
- myExecutionConsole.allowHeavyFilters();
- }
-
- @Override
- public JComponent getComponent() {
- return myExecutionConsole.getComponent();
- }
-
- @Override
- public JComponent getPreferredFocusableComponent() {
- return myExecutionConsole.getPreferredFocusableComponent();
- }
-
@Override
public void dispose() {
- Disposer.dispose(myExecutionConsole);
- }
-
- public SMTestRunnerResultsForm getResultsViewer() {
- return myExecutionConsole.getResultsViewer();
- }
-
- public TestConsoleProperties getProperties() {
- return myExecutionConsole.getProperties();
+ super.dispose();
+ Disposer.dispose(myConsoleView);
}
public SMTestLocator getUrlProvider() {
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java
index 2c9149f87974..b18a193e462a 100644
--- a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java
@@ -26,6 +26,7 @@ import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
import com.intellij.execution.testframework.sm.runner.ui.SMRootTestProxyFormatter;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.sm.runner.ui.TestTreeRenderer;
+import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.diagnostic.Logger;
@@ -76,10 +77,15 @@ public class GradleTestsExecutionConsoleManager
@NotNull final Executor executor,
@NotNull final ExecutionEnvironment env,
@NotNull final ProcessHandler processHandler) throws ExecutionException {
- final GradleConsoleProperties properties = new GradleConsoleProperties(configuration, executor);
- final SMTRunnerConsoleView executionConsole = (SMTRunnerConsoleView)SMTestRunnerConnectionUtil.createAndAttachConsole(
- configuration.getSettings().getExternalSystemId().getReadableName(), processHandler, properties);
- final TestTreeView testTreeView = executionConsole.getResultsViewer().getTreeView();
+ final GradleConsoleProperties consoleProperties = new GradleConsoleProperties(configuration, executor);
+ String testFrameworkName = configuration.getSettings().getExternalSystemId().getReadableName();
+ String splitterPropertyName = SMTestRunnerConnectionUtil.getSplitterPropertyName(testFrameworkName);
+ final GradleTestsExecutionConsole consoleView = new GradleTestsExecutionConsole(consoleProperties, splitterPropertyName);
+ consoleView.initTaskExecutionView(project, processHandler, task.getId());
+ SMTestRunnerConnectionUtil.initConsoleView(consoleView, testFrameworkName);
+ consoleView.attachToProcess(processHandler);
+
+ final TestTreeView testTreeView = consoleView.getResultsViewer().getTreeView();
if (testTreeView != null) {
TestTreeRenderer originalRenderer = ObjectUtils.tryCast(testTreeView.getCellRenderer(), TestTreeRenderer.class);
if (originalRenderer != null) {
@@ -113,7 +119,7 @@ public class GradleTestsExecutionConsoleManager
}
}
- return new GradleTestsExecutionConsole(executionConsole);
+ return consoleView;
}
@Override
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java
index d38268e43485..3dd1b60a12f5 100644
--- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java
@@ -24,6 +24,7 @@ import com.intellij.openapi.externalSystem.model.ExternalSystemException;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
+import com.intellij.openapi.externalSystem.model.task.event.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.text.StringUtil;
@@ -34,9 +35,17 @@ import org.gradle.initialization.BuildLayoutParameters;
import org.gradle.internal.nativeintegration.services.NativeServices;
import org.gradle.process.internal.JvmOptions;
import org.gradle.tooling.*;
+import org.gradle.tooling.events.FailureResult;
+import org.gradle.tooling.events.FinishEvent;
+import org.gradle.tooling.events.SkippedResult;
+import org.gradle.tooling.events.StartEvent;
+import org.gradle.tooling.events.internal.DefaultOperationDescriptor;
+import org.gradle.tooling.events.task.TaskProgressEvent;
+import org.gradle.tooling.events.task.TaskSuccessResult;
import org.gradle.tooling.internal.consumer.DefaultExecutorServiceFactory;
import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.protocol.events.InternalOperationDescriptor;
import org.gradle.tooling.model.build.BuildEnvironment;
import org.gradle.util.GradleVersion;
import org.jetbrains.annotations.NotNull;
@@ -154,12 +163,12 @@ public class GradleExecutionHelper {
operation.setJvmArguments(ArrayUtil.toStringArray(filteredArgs));
}
- if(settings.isOfflineWork()) {
+ if (settings.isOfflineWork()) {
commandLineArgs.add(GradleConstants.OFFLINE_MODE_CMD_OPTION);
}
final Application application = ApplicationManager.getApplication();
- if(application != null && application.isUnitTestMode()) {
+ if (application != null && application.isUnitTestMode()) {
commandLineArgs.add("--info");
commandLineArgs.add("--recompile-scripts");
}
@@ -193,13 +202,59 @@ public class GradleExecutionHelper {
operation.addProgressListener(new org.gradle.tooling.events.ProgressListener() {
@Override
public void statusChanged(org.gradle.tooling.events.ProgressEvent event) {
- listener.onStatusChange(new ExternalSystemTaskNotificationEvent(id, event.getDisplayName()));
+ listener.onStatusChange(convert(id, event));
}
});
operation.setStandardOutput(standardOutput);
operation.setStandardError(standardError);
}
+ @NotNull
+ private static ExternalSystemTaskNotificationEvent convert(ExternalSystemTaskId id, org.gradle.tooling.events.ProgressEvent event) {
+ final InternalOperationDescriptor internalDesc =
+ event.getDescriptor() instanceof DefaultOperationDescriptor ? ((DefaultOperationDescriptor)event.getDescriptor())
+ .getInternalOperationDescriptor() : null;
+ final String eventId = internalDesc == null ? event.getDescriptor().getDisplayName() : internalDesc.getId().toString();
+ final String parentEventId;
+ if (event.getDescriptor().getParent() == null) {
+ parentEventId = null;
+ }
+ else {
+ parentEventId = internalDesc == null ? event.getDescriptor().getParent().getDisplayName() : internalDesc.getParentId().toString();
+ }
+ final String description = event.getDescriptor().getName();
+
+ if (event instanceof StartEvent) {
+ return new ExternalSystemTaskExecutionEvent(
+ id, new ExternalSystemStartEventImpl(eventId, parentEventId, description, event.getEventTime()));
+ }
+ else if (event instanceof FinishEvent) {
+ return new ExternalSystemTaskExecutionEvent(
+ id, new ExternalSystemFinishEventImpl(eventId, parentEventId, description, convert(((FinishEvent)event).getResult())));
+ }
+ else if (event instanceof TaskProgressEvent) {
+ return new ExternalSystemTaskExecutionEvent(
+ id, new BaseExternalSystemProgressEvent(eventId, parentEventId, description, event.getEventTime()));
+ }
+ else {
+ return new ExternalSystemTaskNotificationEvent(id, description);
+ }
+ }
+
+ @NotNull
+ private static OperationResult convert(org.gradle.tooling.events.OperationResult operationResult) {
+ if (operationResult instanceof FailureResult) {
+ return new FailureResultImpl(operationResult.getStartTime(), operationResult.getEndTime());
+ }
+ else if (operationResult instanceof SkippedResult) {
+ return new SkippedResultImpl(operationResult.getStartTime(), operationResult.getEndTime());
+ }
+ else {
+ final boolean isUpToDate = operationResult instanceof TaskSuccessResult && ((TaskSuccessResult)operationResult).isUpToDate();
+ return new SuccessResultImpl(operationResult.getStartTime(), operationResult.getEndTime(), isUpToDate);
+ }
+ }
+
public T execute(@NotNull String projectPath, @Nullable GradleExecutionSettings settings, @NotNull Function f) {
final String projectDir;
@@ -275,8 +330,9 @@ public class GradleExecutionHelper {
};
final File tempFile = writeToFileGradleInitScript(StringUtil.join(lines, SystemProperties.getLineSeparator()));
- BuildLauncher launcher = getBuildLauncher(id, connection, settings, listener, ContainerUtil.newArrayList(),
- ContainerUtil.newArrayList(GradleConstants.INIT_SCRIPT_CMD_OPTION, tempFile.getAbsolutePath()));
+ BuildLauncher launcher = getBuildLauncher(
+ id, connection, settings, listener, ContainerUtil.newArrayList(),
+ ContainerUtil.newArrayList(GradleConstants.INIT_SCRIPT_CMD_OPTION, tempFile.getAbsolutePath()));
launcher.forTasks("wrapper");
launcher.run();
String wrapperPropertyFile = FileUtil.loadFile(wrapperPropertyFileLocation);