Fix conda env auto activation in the terminal (PY-21643)
[idea/community.git] / python / python-terminal / src / com / jetbrains / python / sdk / PyVirtualEnvTerminalCustomizer.kt
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.sdk
17
18 import com.intellij.openapi.components.PersistentStateComponent
19 import com.intellij.openapi.components.ServiceManager
20 import com.intellij.openapi.components.State
21 import com.intellij.openapi.components.Storage
22 import com.intellij.openapi.module.ModuleManager
23 import com.intellij.openapi.options.UnnamedConfigurable
24 import com.intellij.openapi.project.Project
25 import com.intellij.openapi.projectRoots.Sdk
26 import com.intellij.openapi.util.SystemInfo
27 import com.jetbrains.python.run.PyVirtualEnvReader
28 import com.jetbrains.python.run.findActivateScript
29 import org.jetbrains.plugins.terminal.LocalTerminalCustomizer
30 import java.io.File
31 import javax.swing.JCheckBox
32
33 /**
34  * @author traff
35  */
36
37
38 class PyVirtualEnvTerminalCustomizer : LocalTerminalCustomizer() {
39   override fun customizeCommandAndEnvironment(project: Project,
40                                               command: Array<out String>,
41                                               envs: MutableMap<String, String>): Array<out String> {
42     val sdk: Sdk? = findSdk(project)
43
44     if (sdk != null && (PythonSdkType.isVirtualEnv(sdk) || PythonSdkType.isCondaVirtualEnv(
45       sdk)) && PyVirtualEnvTerminalSettings.getInstance(project).virtualEnvActivate) {
46       // in case of virtualenv sdk on unix we activate virtualenv
47       val path = sdk.homePath
48
49       if (path != null) {
50
51         val shellPath = command[0]
52         val shellName = File(shellPath).name
53
54         if (shellName == "bash" || (SystemInfo.isMac && shellName == "sh") || (shellName == "zsh") ||
55             ((shellName == "fish") && PythonSdkType.isVirtualEnv(sdk))) { //fish shell works only for virtualenv and not for conda
56           //for bash we pass activate script to jediterm shell integration (see jediterm-bash.in) to source it there
57           findActivateScript(path, shellPath)?.let { activate ->
58             envs.put("JEDITERM_SOURCE", if (activate.second != null) "${activate.first} ${activate.second}" else activate.first)
59           }
60         }
61         else {
62           //for other shells we read envs from activate script by the default shell and pass them to the process
63           val reader = PyVirtualEnvReader(path)
64           reader.activate?.let {
65             // we add only envs that are setup by the activate script, because adding other variables from the different shell
66             // can break the actual shell
67             envs.putAll(reader.readShellEnv().mapKeys { k -> k.key.toUpperCase() }.filterKeys { k ->
68               k in arrayOf("PATH", "PS1", "VIRTUAL_ENV", "PYTHONHOME", "PROMPT", "_OLD_VIRTUAL_PROMPT", "_OLD_VIRTUAL_PYTHONHOME",
69                            "_OLD_VIRTUAL_PATH")
70             })
71           }
72         }
73       }
74     }
75
76     // for some reason virtualenv isn't activated in the rcfile for the login shell, so we make it non-login
77     return command.filter { arg -> arg != "--login" && arg != "-l" }.toTypedArray()
78   }
79
80
81   private fun findSdk(project: Project): Sdk? {
82     for (m in ModuleManager.getInstance(project).modules) {
83       val sdk: Sdk? = PythonSdkType.findPythonSdk(m)
84       if (sdk != null && !PythonSdkType.isRemote(sdk)) {
85         return sdk
86       }
87     }
88
89     return null
90   }
91
92
93   override fun getDefaultFolder(): String? {
94     return null
95   }
96
97   override fun getConfigurable(project: Project) = object : UnnamedConfigurable {
98     val settings = PyVirtualEnvTerminalSettings.getInstance(project)
99
100     var myCheckbox: JCheckBox = JCheckBox("Activate virtualenv")
101
102     override fun createComponent() = myCheckbox
103
104     override fun isModified() = myCheckbox.isSelected != settings.virtualEnvActivate
105
106     override fun apply() {
107       settings.virtualEnvActivate = myCheckbox.isSelected
108     }
109
110     override fun reset() {
111       myCheckbox.isSelected = settings.virtualEnvActivate
112     }
113   }
114
115
116 }
117
118 class SettingsState {
119   var virtualEnvActivate = true
120 }
121
122 @State(name = "PyVirtualEnvTerminalCustomizer", storages = arrayOf(Storage("python-terminal.xml")))
123 class PyVirtualEnvTerminalSettings : PersistentStateComponent<SettingsState> {
124   var myState = SettingsState()
125
126   var virtualEnvActivate: Boolean
127     get() = myState.virtualEnvActivate
128     set(value) {
129       myState.virtualEnvActivate = value
130     }
131
132   override fun getState() = myState
133
134   override fun loadState(state: SettingsState) {
135     myState.virtualEnvActivate = state.virtualEnvActivate
136   }
137
138   companion object {
139     fun getInstance(project: Project): PyVirtualEnvTerminalSettings {
140       return ServiceManager.getService(project, PyVirtualEnvTerminalSettings::class.java)
141     }
142   }
143
144 }
145