Backup variables should be preserved to be able to deactivate (PY-21560)
[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) && PyVirtualEnvTerminalSettings.getInstance(project).virtualEnvActivate) {
45       // in case of virtualenv sdk on unix we activate virtualenv
46       val path = sdk.homePath
47
48       if (path != null) {
49
50         val shellPath = command[0]
51         val shellName = File(shellPath).name
52
53         if (shellName == "bash" || (SystemInfo.isMac && shellName == "sh") || (shellName == "zsh") || (shellName == "fish")) {
54           //for bash we pass activate script to jediterm shell integration (see jediterm-bash.in) to source it there
55           findActivateScript(path, shellPath)?.let { activate -> envs.put("JEDITERM_SOURCE", activate) }
56         }
57         else {
58           //for other shells we read envs from activate script by the default shell and pass them to the process
59           val reader = PyVirtualEnvReader(path)
60           reader.activate?.let {
61             envs.putAll(reader.readShellEnv().mapKeys { k -> k.key.toUpperCase() }.filterKeys { k ->
62               k in arrayOf("PATH", "PS1", "VIRTUAL_ENV", "PYTHONHOME", "PROMPT", "_OLD_VIRTUAL_PROMPT", "_OLD_VIRTUAL_PYTHONHOME", "_OLD_VIRTUAL_PATH")
63             })
64           }
65         }
66       }
67     }
68
69     // for some reason virtualenv isn't activated in the rcfile for the login shell, so we make it non-login
70     return command.filter { arg -> arg != "--login" && arg != "-l" }.toTypedArray()
71   }
72
73
74   private fun findSdk(project: Project): Sdk? {
75     for (m in ModuleManager.getInstance(project).modules) {
76       val sdk: Sdk? = PythonSdkType.findPythonSdk(m)
77       if (sdk != null && !PythonSdkType.isRemote(sdk)) {
78         return sdk
79       }
80     }
81
82     return null
83   }
84
85
86   override fun getDefaultFolder(): String? {
87     return null
88   }
89
90   override fun getConfigurable(project: Project) = object : UnnamedConfigurable {
91     val settings = PyVirtualEnvTerminalSettings.getInstance(project)
92
93     var myCheckbox: JCheckBox = JCheckBox("Activate virtualenv")
94
95     override fun createComponent() = myCheckbox
96
97     override fun isModified() = myCheckbox.isSelected != settings.virtualEnvActivate
98
99     override fun apply() {
100       settings.virtualEnvActivate = myCheckbox.isSelected
101     }
102
103     override fun reset() {
104       myCheckbox.isSelected = settings.virtualEnvActivate
105     }
106   }
107
108
109 }
110
111 class SettingsState {
112   var virtualEnvActivate = true
113 }
114
115 @State(name = "PyVirtualEnvTerminalCustomizer", storages = arrayOf(Storage("python-terminal.xml")))
116 class PyVirtualEnvTerminalSettings : PersistentStateComponent<SettingsState> {
117   var myState = SettingsState()
118
119   var virtualEnvActivate: Boolean
120     get() = myState.virtualEnvActivate
121     set(value) {
122       myState.virtualEnvActivate = value
123     }
124
125   override fun getState() = myState
126
127   override fun loadState(state: SettingsState) {
128     myState.virtualEnvActivate = state.virtualEnvActivate
129   }
130
131   companion object {
132     fun getInstance(project: Project): PyVirtualEnvTerminalSettings {
133       return ServiceManager.getService(project, PyVirtualEnvTerminalSettings::class.java)
134     }
135   }
136
137 }
138