Sublime Forum

Why is it when I update a default settings value in my plugin folder - it doesn't show up unless I restart Sublime?

#1

As some of you may know I’m working on a major overhaul of my plugin back-end since I’m doing the whole stand-alone plugin thing…

I was under the impression when you updated your settings file and saved it ( default ) the file in memory would see the file has changed and update the value…

Mine doesn’t unless I edit the user file ( then it shows up properly ) or I restart Sublime Text… This is a big problem as I’m porting all of the settings over and I don’t want to use the user files just to have changes seen real time…

I even added sublime-settings support to my plugin reloader - when a plugin settings file has been saved, then it’ll automatically call sublime.load_settings on the file… but the old value continues to persist…

Is there a reason why the default config file ( loaded using load_settings( ‘acms_plugin.sublime-settings’ ) isn’t monitored for changes - especially when developing an addon? but the same file in User/ is?..


The current thing I’m working on is populating the definitions file - basically I’m setting up AccessorFunc system by defining acms_get_settings_accessorfuncs as a Dict, and populating it as FuncName = { key = key_in_config_file, source = ‘settings_file_id’, default = ‘if applicable as default will be in the file so I leave these as null for now unless I decide to set up alternatives’ }, next, next, next, next… etc…

Does anyone know of a helper func to reload the default settings? ( Also my auto reloader doesn’t refresh the user files via load_settings because they aren’t in User/AcecoolCodeMappingSystem/* so I know it isn’t reloading the User settings because of the reloader )…

0 Likes

#2

It actually does in ST3160.

ggs = sublime.load_settings('GitGutter.sublime-settings')
>>> ggs.get('live_mode')
False
reloading settings Packages/GitGutter/GitGutter.sublime-settings
>>> ggs.get('live_mode')
True
reloading settings Packages/GitGutter/GitGutter.sublime-settings
>>> ggs.get('live_mode')
False
0 Likes

#3

Yes, that’s how it’s supposed to work; see for example this video. If it’s not working then Sublime is having problems seeing that the files are being modified.

Possible reasons for that might be something like using symlinks or junctions somewhere along the Data path or possibly even if the related paths were on a network share of some sort.[quote=“Acecool, post:1, topic:35932”]
Does anyone know of a helper func to reload the default settings?
[/quote]

I’m not sure what you mean by this; the sublime.load_settings() API endpoint takes a settings file name (with no path) and gives you a settings object; further calls for the same settings file always return the same settings object as the first time, so in general there is no reason to reload settings and no performance hit in trying to load them every time.

If you wanted to load just the default settings without having the load_settings() call combine all similar settings files together, you can use sublime.load_resource() to get the raw JSON data and sublime.decode_value() to turn it into a Python object. Note that in this case you need to provide the full resource path and not just the filename as with the load_settings() endpoint.

0 Likes

#4

I am using a link - but Sublime Text is able load, and it detects the changes with the callback… interesting… maybe I’ll link it the other way around - I’d prefer to have my project outside of the Sublime Text folder because that gives it too much control…

The type of link I use isn’t a basic link ( which is like a shortcut file ) - ie symbolic… this is a true junction which is the only kind of link which SourceTree can follow properly…

Sublime Text is able to properly detect the files within and it knows the actual folder it is looking at… It could be why I am having some other odd issues… interesting…

I tried what you did for the file and it only reloaded the settings file if it was the client one inside of the user folder…

Question though - what is the function which calls the reloading settings file? I made a file reload detector to automatically reload the files when a reload is detected so I don’t need reload plugin added to the other files and it works fine… I tried doing the same for settings but load_settings just returns what’s in memory if it doesn’t see the changes…

>>> acms = sublime.load_settings( 'ACMS_Plugin.sublime-settings' )
>>> acms.get( 'developer_mode' )
True
>> Dynamic Package File Reloader > On Save Event Triggered for Package Settings File: "C:\Users\Acecool\AppData\Roaming\Sublime Text 3\Packages\AcecoolCodeMappingSystem\ACMS_Plugin.sublime-settings" - Which converts to sublime.load_settings: "ACMS_Plugin.sublime-settings"
>>> acms.get( 'developer_mode' )
True
#### Main Plugin Auto-Refresh System - Josh 'Acecool' Moser
##


##
## Imports
##

##
import sublime, sublime_plugin

##
import os


##
## This Event Listener is specifically to enable auto-refreshing of plugin core files when they're edited... It is purposefully kept separate so others can learn from it / use as a drop-in module for thier own plugins...
##
class AcecoolCodeMappingSystemDynamicPluginReloaderEventListener( sublime_plugin.EventListener ):
	##
	## Important Data...
	##

	## The package name used for reloading files listed below...
	package_name				= 'AcecoolCodeMappingSystem'

	## Packages Folder Name ( For generating id when not a part of our package )
	packages_folder				= 'Packages'

	## The file-type to monitor changes from ( ie files we re-import so the changes are relfected in memory - If the file extension isn't this, then we don't return ANY name )
	monitored_extensions		= { 'py': True, 'sublime-settings': True }

	## Simple text variant of -1, for the last element in a list..
	get_last_element			= -1

	## Returns all text from n to - 3rd char or up until '.py'... this is a simple text helper to clarify that..
	subtract_ext_from_text		= -3 # -( len( monitored_extension ) + 1 )

	## The text we monitor to determine whether or not the updated file is in the User\ folder..
	monitored_user_text			= 'User.'

	## Simple helper for defining 'User.' len
	shift_by_user_dot_length	= 5 # len( monitored_user_text )


	##
	## Returns the plugin name for use in importing it...
	##
	def GetPluginNameFromPath( self, _path = '' ):
		## Grab the extension of the saved file - note this doesn't work on files with more than 1 extension or decimal - as per Python file-naming / import conventions, do not add decimals to the file-name. Only use 1 extension such as file.ext - anything else will not be importable using the import call, you'll need to manually execute the code behind the import call which is messy and can lead to issues if you're unfamiliar with the system and it can lead to other problems..
		_ext = _path.split( '.' )[ self.get_last_element ]

		## Determine the index of the package name, if it exists..
		_index = _path.find( self.package_name )

		## Helper - If True then it is part of our package, if False then it isn't ( user can decide what to do )
		_is_plugin_file	= ( _index > 0 )

		## If the file isn't from our package, make sure our index var is set correctly so we can still generate the correct string by removing *Packages\ from the output...
		if ( not _is_plugin_file ):
			_index = _path.find( self.packages_folder ) + len( self.packages_folder ) + 1

		## If we've saved a python file inside of our Package, then we assemble the include...
		if ( self.monitored_extensions.get( _ext, None ) ):
			## Note: Either of the following _plugin assignments will work, simply uncomment one and comment the other - the comment details exactly what happens with each example...

			if ( _ext == 'sublime-settings' ):
				return ( _path.split( '\\' )[ - 1 ], _is_plugin_file )
			else:
				## For simplicty convert all folders to decimals...
				_path = _path.replace( '\\', '.' )

				## This method takes Path\To\||PackageName\InternalFolders\FileName||[[.py]] - [[...]] being subtracted from the return using -3, ||...|| being what's captured starting with _index + len( ... ) through -3... - After we capture what's needed ignoring the rest, we replace backslashes with decimals.
				_plugin = _path[ _index : self.subtract_ext_from_text ]

				## This method takes: Path\To\PackageName||\Internal\Folders\FileName||[[.py]] - we leave the left \\ ( by not adding + 1 to _index + _len ) to convert it to a decimal so we don't need to manually add it... [[...]] being subtracted from the return using -3, ||...|| being what's captured starting with _index + len( ... ) through -3...
				## _plugin = self.package_name + _path[ _index + len( self.package_name ) : -3 ]

				## User Folder Clause - If User is found in the file-name, and the index is LESS than the name of the package-name, then we are in Packages\User\ACMS\ so we need to account for that...
				_plugin_user = _path[ _index - self.shift_by_user_dot_length : self.subtract_ext_from_text ]
				if ( _plugin_user.startswith( self.monitored_user_text ) ):
					_plugin = self.monitored_user_text + _plugin

				return ( _plugin, _is_plugin_file )

		## Default - If the file saved isn't a Python file...
		return ( None, _is_plugin_file )


	##
	## When a package Python file has been saved, we auto-refresh only that particular file... - Note: If you update this function you will need to save twice - the first time uses the current method in memory for output, and the second time shows you what the function has become...
	##
	def on_post_save_async( self, _view ):
		## Grab the file-name of the file which was saved
		_file = _view.file_name( )

		## Grab the clean plugin import string / name and whether or not the file is part of our library so we can choose whether or not to include it ( or to call some other callback in our plugin with this information )
		( _plugin, _is_plugin_file ) = self.GetPluginNameFromPath( _file )

		## If the file saved is a plugin file, and a name was generated then re-import the files to allow the changes made in code to be reflected by code held in memory...
		if ( _is_plugin_file and _plugin != None ):

			if ( _plugin.endswith( 'py' ) ):
				## Print it out to make sure it works..
				print( '>> Dynamic Package File Reloader > On Save Event Triggered for Package File: "' + _file + '" - Which converts to Import Plugin: "' + _plugin + '"' )

				## Reload our Package File...
				sublime_plugin.reload_plugin( _plugin )
			elif ( _plugin.endswith( 'sublime-settings' ) ):
				## Print it out to make sure it works..
				print( '>> Dynamic Package File Reloader > On Save Event Triggered for Package Settings File: "' + _file + '" - Which converts to sublime.load_settings: "' + _plugin + '"' )

				sublime.load_settings( _plugin )

Is there no way to get the junction to work? It’s supposed to work just like an actual folder unlike symbolic links and shouldn’t be any different…

The files all load properly - maybe I need to set permissions for that folder…

If I put the project in Sublime Text then it’s just outside of my projects folder and means I’m less efficient when backing things up because I have to dig around my PC for all of the data ( although the link in reverse would let me properly copy or zip the data as if a folder was there but still - I like having the originals all in my projects folder )…

When my project updates, it updates so why is it plugin files can update but the settings files can’t?

0 Likes

#5

What holds true for symlinks also holds true for Junctions on windows. If you’re a registered user, the most recent development builds have proper support for this added, so if you upgrade your problem should go away; otherwise you’ll have to wait until the next stable build.

I assume that’s because your User package isn’t a link; if it was, it wouldn’t work there either.

I presume it’s something in the core because it’s the core that’s using inotify (or the OS equivalent) to know when files are changed; when it sees a plugin file changed it reloads the plugin and when it sees a settings or keymap or menu change, it reloads them in memory.

As previously mentioned, the first time you call sublime.load_settings(), the settings are loaded into a settings object and cached internally; any subsequent call to sublime.load_settings() returns the same object as it did the first time. If the settings file changes on disk (and Sublime is able to detect it), then it reloads the settings and updates the settings object in memory.

It doesn’t tell code explicitly if the settings changed because you don’t need to know; 99% of the time your code can just refer to the settings object and always get the current value even if the file updated since you loaded it. For that last 1% where you want to know when a setting changed, use settings.add_on_change() to register your interest in knowing that the settings changed.

0 Likes

#6

Just out of curiosity, too much control over what? If it wanted to mess with your files, couldn’t it just follow the link?

0 Likes

#7

Too much control in the sense that I prefer to keep my projects isolated. It makes them easier to back up, modify, manage, etc… By splitting them up - and yes I could make a link the other way but I like to remove the directory some times to test issues to see if they’re caused by another mod, or from sublime text and it makes it easier by not having to worry about which files to not delete…

If it’s fixed the next version over then I can wait.

0 Likes