Provide settings for the virtualenv activation in terminal (PY-21583)
[idea/community.git] / plugins / terminal / src / org / jetbrains / plugins / terminal / TerminalProjectOptionsProvider.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 org.jetbrains.plugins.terminal
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.diagnostic.Logger
23 import com.intellij.openapi.project.Project
24 import com.intellij.openapi.roots.ProjectRootManager
25 import com.intellij.openapi.util.SystemInfo
26 import java.io.File
27 import kotlin.reflect.*
28
29 /**
30  * @author traff
31  */
32 @State(name = "TerminalProjectOptionsProvider", storages = arrayOf(Storage("terminal.xml")))
33 class TerminalProjectOptionsProvider(val project: Project) : PersistentStateComponent<TerminalProjectOptionsProvider.State> {
34
35   private val myState = State()
36
37   override fun getState(): State? {
38     return myState
39   }
40
41   override fun loadState(state: State) {
42     myState.myShellPath = state.myShellPath
43     myState.myStartingDirectory = state.myStartingDirectory
44   }
45
46   class State {
47     var myShellPath: String? = null
48
49     var myStartingDirectory: String? = null
50   }
51
52   var shellPath: String? by ValueWithDefault(State::myShellPath, myState) { defaultShellPath }
53
54   var startingDirectory: String? by ValueWithDefault(State::myStartingDirectory, myState) { defaultStartingDirectory }
55
56   val defaultStartingDirectory: String?
57     get() {
58       var directory: String? = null
59       for (customizer in LocalTerminalCustomizer.EP_NAME.extensions) {
60         try {
61
62           if (directory == null) {
63             directory = customizer.defaultFolder
64           }
65         }
66         catch (e: Exception) {
67           LOG.error("Exception during getting default folder", e)
68         }
69       }
70
71       return directory ?: currentProjectFolder()
72     }
73
74
75   private fun currentProjectFolder(): String? {
76     val projectRootManager = ProjectRootManager.getInstance(project)
77
78     val roots = projectRootManager.contentRoots
79     if (roots.size == 1) {
80       roots[0].canonicalPath
81     }
82     val baseDir = project.baseDir
83     return baseDir?.canonicalPath
84   }
85
86   val defaultShellPath: String
87     get() {
88       val shell = System.getenv("SHELL")
89
90       if (shell != null && File(shell).canExecute()) {
91         return shell
92       }
93
94       if (SystemInfo.isUnix) {
95         if (File("/bin/bash").exists()) {
96           return "/bin/bash"
97         }
98         else {
99           return "/bin/sh"
100         }
101       }
102       else {
103         return "cmd.exe"
104       }
105     }
106
107   companion object {
108     private val LOG = Logger.getInstance(TerminalProjectOptionsProvider::class.java)
109
110
111     fun getInstance(project: Project): TerminalProjectOptionsProvider {
112       return ServiceManager.getService(project, TerminalProjectOptionsProvider::class.java)
113     }
114   }
115
116 }
117
118 // TODO: In Kotlin 1.1 it will be possible to pass references to instance properties. Until then we need 'state' argument as a reciever for
119 // to property to apply
120 class ValueWithDefault(val prop: KMutableProperty1<TerminalProjectOptionsProvider.State, String?>, val state: TerminalProjectOptionsProvider.State, val default: () -> String?) {
121   operator fun getValue(thisRef: Any?, property: KProperty<*>): String? {
122     return if (prop.get(state) !== null) prop.get(state) else default()
123   }
124
125   operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
126     prop.set(state, if (value == default() || value.isNullOrEmpty()) null else value)
127   }
128 }
129
130
131